• 系统的扩展性(怎么设计插件)(转)


    本文谈下我个人对“系统的扩展性”的看法
    首先需要声明,这里的扩展性不是指伸缩性(scalability),而是指灵活性(flexibility)
    一、名词解释
    这里有2个关键词,一个是系统,一个是扩展性
    那么要说明这个主题,就先要解释一下这2个关键词。按照我的习惯,还是从最小的东西开始举例子
    “系统”,我认为就是三要素,输入,逻辑,输出。按照这个定义,最小的系统就是一个方法

    Java代码 复制代码 收藏代码
    1. public String sayHi(String name){   
    2.     return "hi "+ name;   
    3. }  
    public String sayHi(String name){
        return "hi "+ name;
    }
    

    我认为这个方法就是一个系统,输入是name,逻辑是加上一个"hi ",输出也是一个字符串
    这三要素可以为空,比如有无参数的方法,无返回值的方法,或者没有业务逻辑的方法。。在极端情况下,甚至可以三要素都为空,不过就不具现实意义了

    Java代码 复制代码 收藏代码
    1. public void doNothing(){   
    2. }  
    public void doNothing(){
    }
    

    这个方法没有意义,不过不可否认它也是符合语法的,也有三要素,只不过都为空而已,所以我认为这个也可以算一个系统
    因此,从方法往上延伸,类和模块也是系统;淘宝商城,去掉展现层,也是系统;常用的spring,一般认为是个框架,不过其实它也是系统;tomcat是一个servlet容器,但是它也是系统
    下面解释我理解的扩展性。这个词我给不出精确的定义,所以只能用大白话加例子来说明。我认为扩展性就是“可变化”,或者说“没写死”
    比如说这个方法,我认为没有扩展性

    Java代码 复制代码 收藏代码
    1. public void sayHi(){   
    2.     System.out.println("hi kyfxbl");   
    3. }  
    public void sayHi(){
        System.out.println("hi kyfxbl");
    }
    

    不管怎么调用,它也只能在屏幕上打出hi kyfxbl这句话,所以是“不可变”的,“写死了”,没有扩展性 加一个参数,就有扩展性了

    Java代码 复制代码 收藏代码
    1. public void sayHi(String name){   
    2.     System.out.println("hi " + name);   
    3. }  
    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就没有影响
    这里要说到接口的第二个作用,就是它关系到系统的扩展性。拿一段代码举例子:

    Java代码 复制代码 收藏代码
    1. public void process(){   
    2.     ComponentA a = new ComponentA();   
    3.     a.doProcess();   
    4. }  
    public void process(){
        ComponentA a = new ComponentA();
        a.doProcess();
    }
    

    这个方法就没有扩展性了,因为不管在任何情况下,调用process()方法,执行序列都是一样的。要想改变它,除非拿到源码,把ComponentA的实现改掉。当然在实际中,这是不可能的
    那么如果是这样写

    Java代码 复制代码 收藏代码
    1. public void process(){   
    2.     Component a = searchForComponent();   
    3.     a.doProcess();   
    4. }  
    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点只是胡思乱想

  • 相关阅读:
    Clustering by fast search and find of density peaks
    《第一行代码》(二)
    TF-IDF
    《第一行代码》(一)
    《OpenCV入门》(三)
    OpenCV入门(二)
    协方差矩阵特征向量的意义
    ICA
    整数划分
    1144. The Missing Number (20)
  • 原文地址:https://www.cnblogs.com/lenmom/p/3255108.html
Copyright © 2020-2023  润新知