天天看点

设计模式(10)--Facade(外观模式)--结构型

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

作者QQ:1095737364    QQ群:123300273     欢迎加入!

1.模式定义:

  外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。使用外观模式时,我们创建了一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以直接通过外观类来调用内部子系统中方法,从而外观模式让客户和子系统之间避免了紧耦合。

2.模式特点:

  外观模式的一般描述是:外观模式定义了一个高层的功能,为子系统中的多个模块协同的完成某种功能需求提供简单的对外功能调用方式,使得这一子系统更加容易被外部使用。

  外观模式的目的不是给予子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。

  外观模式的本质是:封装交互,简化调用。

  外观模式,为子系统的一组接口提供一个统一的接口,该模式定义了一个高层接口,这一个高层接口使的子系统更加容易使用。并且外观模式可以解决层结构分离、降低系统耦合度和为新旧系统交互提供接口功能。

  使用了外观模式之后,客户端只依赖与外观类,从而将客户端与子系统的依赖解耦了,如果子系统发生改变,此时客户端的代码并不需要去改变。外观模式的实现核心主要是——由外观类去保存各个子系统的引用,实现由一个统一的外观类去包装多个子系统类,然而客户端只需要引用这个外观类,然后由外观类来调用各个子系统中的方法。然而这样的实现方式非常类似适配器模式,然而外观模式与适配器模式不同的是:适配器模式是将一个对象包装起来以改变其接口,而外观是将一群对象 ”包装“起来以简化其接口。它们的意图是不一样的,适配器是将接口转换为不同接口,而外观模式是提供一个统一的接口来简化接口。

3.使用场景:

(1)为复杂系统 提供简单的接口。

(2) 客户程序与抽象类的实现部分分离。

(3)构建层次系统时,用作入口。需要将设计进行分层时考虑Facade模式。

(4)在开发阶段,子系统往往因为重构变得越来越复杂,增加外观模式可以提供一个简单的接口,减少它们之间的依赖。

(5) 在维护一个遗留的大型系统时,可以这个系统已经非常难以维护和扩展,可以为新系统开发一个Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。

4.模式实现:

  (1)外观模式的结构

    由于外观模式的结构图过于抽象,因此把它稍稍具体点。假设子系统内有三个模块,分别是ModuleA、ModuleB和ModuleC,它们分别有一个示例方法,那么此时示例的整体结构图如下:

设计模式(10)--Facade(外观模式)--结构型

 在这个对象图中,出现了两个角色:

    [1]外观(Facade)角色 :

    客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。

public class Facade {
    //示意方法,满足客户端需要的功能
    public void test(){
        ModuleA a = new ModuleA();
        a.testA();
        ModuleB b = new ModuleB();
        b.testB();
        ModuleC c = new ModuleC();
        c.testC();
    }
}      

    [2]子系统(SubSystem)角色 :

    可以同时有一个或者多个子系统。每个子系统都不是一个单独的类,而是一个类的集合(如上面的子系统就是由ModuleA、ModuleB、ModuleC三个类组合而成)。每个子系统都可以被客户端直接调用,或者被外观角色调用。子系统并不知道外观的存在,对于子系统而言,外观仅仅是另外一个客户端而已。

public class ModuleA {
    //示意方法
    public void testA(){
        System.out.println("调用ModuleA中的testA方法");
    }
}
public class ModuleB {
    //示意方法
    public void testB(){
        System.out.println("调用ModuleB中的testB方法");
    }
}
public class ModuleC {
    //示意方法
    public void testC(){
        System.out.println("调用ModuleC中的testC方法");
    }
}      

    [3]客户端角色类:

public class Client {
    public static void main(String[] args) {        
        Facade facade = new Facade();
        facade.test();
    }
}      

    Facade类其实相当于A、B、C模块的外观界面,有了这个Facade类,那么客户端就不需要亲自调用子系统中的A、B、C模块了,也不需要知道系统内部的实现细节,甚至都不需要知道A、B、C模块的存在,客户端只需要跟Facade类交互就好了,从而更好地实现了客户端和子系统中A、B、C模块的解耦,让客户端更容易地使用系统。

  (2)外观模式的实现

    使用外观模式还有一个附带的好处,就是能够有选择性地暴露方法。一个模块中定义的方法可以分成两部分,一部分是给子系统外部使用的,一部分是子系统内部模块之间相互调用时使用的。有了Facade类,那么用于子系统内部模块之间相互调用的方法就不用暴露给子系统外部了。

   [1]子系统(SubSystem)角色 :

public class Module {
    /**
     * 提供给子系统外部使用的方法
     */
    public void a1(){};    
    /**
     * 子系统内部模块之间相互调用时使用的方法
     */
    public void a2(){};
    public void a3(){};
}
public class ModuleB {
    /**
     * 提供给子系统外部使用的方法
     */
    public void b1(){};    
    /**
     * 子系统内部模块之间相互调用时使用的方法
     */
    public void b2(){};
    public void b3(){};
}
public class ModuleC {
    /**
     * 提供给子系统外部使用的方法
     */
    public void c1(){};    
    /**
     * 子系统内部模块之间相互调用时使用的方法
     */
    public void c2(){};
    public void c3(){};
}      

    [2]外观(Facade)角色 :

public class ModuleFacade {
    
    ModuleA a = new ModuleA();
    ModuleB b = new ModuleB();
    ModuleC c = new ModuleC();
    /**
     * 下面这些是A、B、C模块对子系统外部提供的方法
     */
    public void a1(){
        a.a1();
    }
    public void b1(){
        b.b1();
    }
    public void c1(){
        c.c1();
    }
}      

    这样定义一个ModuleFacade类可以有效地屏蔽内部的细节,免得客户端去调用Module类时,发现一些不需要它知道的方法。比如a2()和a3()方法就不需要让客户端知道,否则既暴露了内部的细节,又让客户端迷惑。对客户端来说,他可能还要去思考a2()、a3()方法用来干什么呢?其实a2()和a3()方法是内部模块之间交互的,原本就不是对子系统外部的,所以干脆就不要让客户端知道。

一个系统可以有几个外观类

    在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只有一个外观类,而仅仅是说对每一个子系统只有一个外观类。或者说,如果一个系统有好几个子系统的话,每一个子系统都有一个外观类,整个系统可以有数个外观类。

为子系统增加新行为

    初学者往往以为通过继承一个外观类便可在子系统中加入新的行为,这是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向子系统加入新的行为。比如医院中的接待员并不是医护人员,接待员并不能为病人提供医疗服务。

  (3)外观模式在Tomcat中的使用

    Tomcat中外观模式使用的很多,因为Tomcat中有很多不同组件,每个组件要相互通信,但是又不能将自己内部数据过多的暴露给其他组件。外观面模式隔离数据是个很好的方法。

    下面是Request上使用的外观模式:

设计模式(10)--Facade(外观模式)--结构型

    使用过Servlet的人都清楚,除了要在web.xml做相应的配置外,还需继承一个叫HttpServlet的抽象类,并且重写doGet与doPost方法(当然只重写service方法也是可以的)。

public class TestServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {        
        this.doPost(request, response);            
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {                   
    }
}      

    可以看出doGet与doPost方法有两个参数,参数类型是接口HttpServletRequest与接口HttpServletResponse,那么从Tomcat中传递过来的真实类型到底是什么呢?通过debug会发现,在真正调用TestServlet类之前,会经过很多Tomcat中的方法。如下图所示

设计模式(10)--Facade(外观模式)--结构型

  注意红色方框圈中的类,StandardWrapperValue类中的invoke方法225行代码如下:

      filterChain.doFilter(request.getRequest(), response.getResponse());

    在StandardWrapperValue类中并没有直接将Request对象与Response对象传递给ApplicationFilterChain类的doFilter方法,传递的是RequestFacade与ResponseFacade对象,为什么这么说呢,看一下request.getRequest()与response.getResponse()方法就真相大白了。

     [1]Request类

public HttpServletRequest getRequest() {
        if (facade == null) {
            facade = new RequestFacade(this);
        }
        return facade;
}      

     [2]Response类

public HttpServletResponse getResponse() {
        if (facade == null) {
            facade = new ResponseFacade(this);
        }
        return (facade);
}      

    可以看到它们返回都是各自的一个外观类,那么这样做有什么好处呢?

    Request对象中的很多方法都是内部组件之间相互交互时使用的,比如setComet、setRequestedSessionId等方法(这里就不一一列举了)。这些方法并不对外部公开,但是又必须设置为public,因为还需要跟内部组件之间交互使用。最好的解决方法就是通过使用一个Facade类,将与内部组件之间交互使用的方法屏蔽掉,只提供给外部程序感兴趣的方法。

    如果不使用Facade类,直接传递的是Request对象和Response对象,那么熟悉容器内部运作的程序员可以分别把ServletRequest和ServletResponse对象向下转换为Request和Response,并调用它们的公共方法。比如拥有Request对象,就可以调用setComet、setRequestedSessionId等方法,这会危害安全性。

5.优缺点:

  (1)外观模式的优点:

   [1]松散耦合

    外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。

   [2]简单易用

    外观模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观类交互就可以了。

   [3]更好的划分访问层次

    通过合理使用Facade,可以帮助我们更好地划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好地隐藏了内部的细节。

  (2)外观模式的缺点:

[1]如果增加新的子系统可能需要修改外观类或客户端的源代码,这样就违背了”开——闭原则“(不过这点也是不可避免)。

[2]过多的或者是不太合理的Facade也容易让人迷惑,到底是调用Facade好呢,还是直接调用模块好。

[3]不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。

[4]在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

继续阅读