浏览 3740 次
锁定老帖子 主题:系统的扩展性(怎么设计插件)
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-12-12
首先需要声明,这里的扩展性不是指伸缩性(scalability),而是指灵活性(flexibility) 一、名词解释 这里有2个关键词,一个是系统,一个是扩展性 那么要说明这个主题,就先要解释一下这2个关键词。按照我的习惯,还是从最小的东西开始举例子 “系统”,我认为就是三要素,输入,逻辑,输出。按照这个定义,最小的系统就是一个方法 public String sayHi(String name){ return "hi "+ name; } 我认为这个方法就是一个系统,输入是name,逻辑是加上一个"hi ",输出也是一个字符串 这三要素可以为空,比如有无参数的方法,无返回值的方法,或者没有业务逻辑的方法。。在极端情况下,甚至可以三要素都为空,不过就不具现实意义了 public void doNothing(){ } 这个方法没有意义,不过不可否认它也是符合语法的,也有三要素,只不过都为空而已,所以我认为这个也可以算一个系统 因此,从方法往上延伸,类和模块也是系统;淘宝商城,去掉展现层,也是系统;常用的spring,一般认为是个框架,不过其实它也是系统;tomcat是一个servlet容器,但是它也是系统 下面解释我理解的扩展性。这个词我给不出精确的定义,所以只能用大白话加例子来说明。我认为扩展性就是“可变化”,或者说“没写死” 比如说这个方法,我认为没有扩展性 public void sayHi(){ System.out.println("hi kyfxbl"); } 不管怎么调用,它也只能在屏幕上打出hi kyfxbl这句话,所以是“不可变”的,“写死了”,没有扩展性 加一个参数,就有扩展性了 public void sayHi(String name){ System.out.println("hi " + name); } 这个方法根据被调用时接受参数的不同,会呈现出不同的结果,就有扩展性了。结合第一点说的,方法也是系统,那么可以认为,这个方法,是一个最小的“有扩展性的系统” 二、系统即容器 上面为了解释清楚“系统”和“扩展性”的意思,选了很极端的例子,没有什么实际意义。现在就回到常见的场景中 还是以tomcat来举例子,tomcat作为一个servlet容器,也是一个运行的系统,而且是一个扩展性非常强的系统 当tomcat启动并加载一个web app的时候,它并不知道这个web app有哪些servlet规范规定的组件。有多少个Servlet呢,有没有Listener呢,都不清楚 但是当它启动web app初始化流程的时候,它就找到ContextListener的组件,然后实例化,再调用生命周期方法。所以如果我们的web app应用没有ContextListener,那么就不会执行;如果有一个,就执行一个;如果有两个,就执行两个…… 这种感觉就像是,我们在给tomcat写插件一样,或者说我们在根据servlet规范的要求,写一些组件,然后放到servlet容器里运行起来 再举一个spring的例子,spring是常见框架,并且也是一个扩展性很强的系统。大部分情况下,我们只是使用spring提供的功能,比较少会去扩展它。但是实际上是可以扩展的。比如在ApplicationContext初始化的过程中,它会去调用PostBeanDefinitionProcess,PostBeanDefinitionProcess是一个接口,我们完全可以自己增加一个自定义的PostBeanDefinitionProcess实现类,放到配置文件里,那么也就会被spring给执行了 这种情况下,spring的各个组件实际上都是在spring这个容器中运行的。spring容器本身运行的流程,是spring设计者规定的(相当于servlet的规范)。同时,设计者在流程中预先就保留了一些扩展点,等着后来的人(spring的用户)去自行扩展 因此,spring能不能扩展,能在哪些环节被扩展,是在设计的时候就决定的。试想如果rod johnson在一开始,就没有设计查找并执行PostBeanDefinitionProcess这个环节,那我们就不能在这个环节上对spring系统进行扩展了 日常我们写的系统,也是这样。以后能不能扩展,在哪里扩展,怎么扩展,都是在设计的时候就决定的。如果系统中,某个环节调用某个组件,都被固定下来,是“写死的”,那么这个环节就不具扩展性了。当然这个时候,系统依然是容器,只是容器中的组件是不可变的 三、接口即设计 因此,为什么说接口就是设计呢,我认为就在这里。以前另外一篇博客,也谈到过接口的作用。当时我说的是,接口可以使两个模块之间的依赖减小。比如模块A依赖模块B,但是A只依赖B中的一个接口,那么不管模块B的内部实现怎么变,只要这个接口是稳定的,对A就没有影响 这里要说到接口的第二个作用,就是它关系到系统的扩展性。拿一段代码举例子: public void process(){ ComponentA a = new ComponentA(); a.doProcess(); } 这个方法就没有扩展性了,因为不管在任何情况下,调用process()方法,执行序列都是一样的。要想改变它,除非拿到源码,把ComponentA的实现改掉。当然在实际中,这是不可能的 那么如果是这样写 public void process(){ Component a = searchForComponent(); a.doProcess(); } searchForComponent()方法的逻辑,是从配置文件里找到一个Component接口的实现类,实例化并返回,那么这个方法就很有扩展性了 随时都可以自行实现一个实现Component接口的实现类,比如MySpecialComponent,放到配置文件里。那么这个方法运行时的逻辑,就完全是灵活的了 接口Component在这里,就是充当了一个占位符的作用,但同时又把整体的流程确定了下来,先找到实例-->调用实例的方法。我们可以随意扩展Component,但是这个整体的流程却是不会改变的 联系上面的内容,这个process()方法(系统),也是一个容器。Component就是它的组件,是不确定的 想想前面举的tomcat和spring的例子,它们之所以有扩展性,就是因为容器本身的代码,用的是接口(ContextListener和PostBeanDefinitionProcess)来占位,而没有用实际的类把逻辑“写死”。但是整体的流程,是固定下来的 这里就体现了接口,对于设计的意义 四、可扩展性的要素 总结上面的例子,一个系统是否具有扩展性,是在设计之初就固定下来的。 那么系统要有扩展性,就至少需要3个要素: 1、在规定了流程的前提下,允许某些环节扩展 2、将允许扩展的部分,以API的方式对外部提供 3、对外部提供规则 第1条前面已经说了很多了,第2条和第3条也很简单 servlet和spring是可以扩展的,但是如果没有拿到ContextListener和PostBeanDefinitionProcess这2个接口,又怎么能写出实现类呢。那也就只能看着系统运行默认组件了 但是提供API,不需要把整个系统的接口都暴露出去,提供必需的子集即可。比如只需要有servlet-api.jar,就可以开发servlet应用了,并不需要拿到catalina.jar。tomcat的源码里,大部分都是容器自身的实现,允许扩展的部分,仅仅通过servlet-api.jar来提供就足够了 对外部提供规则,就是告诉外部要怎么扩展。容器是别人设计的,用户怎么知道要怎么扩展呢?当然就需要容器的设计者来提供信息。告诉用户,你可以实现ContextListener接口,然后在web.xml里配置一下…… 五、所谓的插件 写本文的原因,其实是最近在分析一个系统。这两天在研究它的插件体系,想到这么多就写下来 搞清楚上面的内容,插件也就不复杂了 从用户的角度来看,要写一个插件,就是拿到API,然后按照规则写扩展组件 从系统(容器)设计者的角度看,我的系统要支持插件扩展,就是: 1、规定流程,设计扩展点(包括加载机制) 2、把扩展点打包成API,作为二次开发的SDK提供给用户 3、告诉用户,应该怎么使用这个API 好吧,其实第5点才是我想总结的,前面的4点只是胡思乱想 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-12-12
个人观点:
1、设定好需要解决的问题; 2、归类总结要解决的模型; 3、尽可能多的提取不变的要素; 4、为可能变化的要素提供扩展方案; 尽量在不需要自定义扩展的情况下可以满足最多的需求。 |
|
返回顶楼 | |
发表时间:2012-12-13
尽量在不需要自定义扩展的情况下可以满足最多的需求。
这话其实是有点歧义,不知道是不是你没有说清楚。 应该是一个内核提供基础的非业务功能,然后在这个基础上,提供尽可能多的扩展来满足需求。 |
|
返回顶楼 | |
发表时间:2012-12-14
wl95421 写道 尽量在不需要自定义扩展的情况下可以满足最多的需求。
这话其实是有点歧义,不知道是不是你没有说清楚。 应该是一个内核提供基础的非业务功能,然后在这个基础上,提供尽可能多的扩展来满足需求。 正是此意。 |
|
返回顶楼 | |