#0 系列目录#算法
#1 场景问题# ##1.1 生活中的示例## 外观模式在现实生活中的示例不少,好比:组装电脑,一般会有两种方案。设计模式
一个方案是去电子市场把本身须要的配件都买回来,而后本身组装,绝对DIY(Do It Yourself)。这个方案好是好,可是须要对各类配件都要比较熟悉,这样才能选择最合适的配件,并且还要考虑配件之间的兼容性。如图所示:架构
另一个方案,就是到电子市场,找一家专业装机的公司,把具体的要求一讲,而后就等着拿电脑就行了。固然价格会比本身所有DIY贵一些,但综合起来这还算是个不错的选择,估计也是大多数人的选择。如图所示:工具
这个专业的装机公司就至关于本章的主角——Facade。有了它,咱们就不用本身去跟众多卖配件的公司打交道,只须要跟装机公司交互就能够了,由装机公司去跟各个卖配件的公司交互,并组装好电脑返回给咱们。学习
把上面的过程抽象一下,若是把电子市场当作是一个系统,而各个卖配件的公司当作是模块的话,就相似于出现了这样一种状况:客户端为了完成某个功能,须要去调用某个系统中的多个模块,把它们称为是A模块、B模块和C模块吧,对于客户端而言,那就须要知道A、B、C这三个模块的功能,还须要知道如何组合这多个模块提供的功能来实现本身所须要的功能,很是麻烦。ui
要是有一个简单的方式能让客户端去实现相同的功能多好啊,这样,客户端就不用跟系统中的多个模块交互,并且客户端也不须要知道那么多模块的细节功能了,实现这个功能的就是Facade。this
##1.2 代码生成的应用## 不少公司都有这样的应用工具,能根据配置生成代码。通常这种工具都是公司内部使用,较为专有的工具,生成的可能是按照公司的开发结构来实现的常见的基础功能,好比增删改查。这样每次在开发实际应用的时候,就能够以很快的速度把基本的增删改查实现出来,而后把主要的精力都放在业务功能的实现上。.net
固然这里不可能去实现一个这样的代码生成工具,那须要整本书的内容,这里仅用它来讲明外观模式。设计
假设使用代码生成出来的每一个模块都具备基本的三层架构,分为:表现层、逻辑层和数据层,那么代码生成工具里面就应该有相应的代码生成处理模块。代理
另外,代码生成工具自身还须要一个配置管理的模块,经过配置来告诉代码生成工具,每一个模块究竟须要生成哪些层的代码,好比:经过配置来描述,是只须要生成表现层代码呢,仍是三层都生成。具体的模块示意如图所示:
那么如今客户端须要使用这个代码生成工具来生成须要的基础代码,该如何实现呢?
##1.3 不用模式的解决方案## 有朋友会想,开发一个这样的工具或许会比较麻烦,可是使用一下,应该不难吧,直接调用不就能够了。
在示范客户端以前,先来把工具模拟示范出来,为了简单,每一个模块就写一个类,并且每一个类只是实现一个功能,仅仅作一个示范。
/** * 示意配置描述的数据Model,真实的配置数据会不少 */ public class ConfigModel { /** * 是否须要生成表现层,默认是true */ private boolean needGenPresentation = true; /** * 是否须要生成逻辑层,默认是true */ private boolean needGenBusiness = true; /** * 是否须要生成DAO,默认是true */ private boolean needGenDAO = true; public boolean isNeedGenPresentation() { return needGenPresentation; } public void setNeedGenPresentation( this.needGenPresentation = needGenPresentation; } public boolean isNeedGenBusiness() { return needGenBusiness; } public void setNeedGenBusiness(boolean needGenBusiness) { this.needGenBusiness = needGenBusiness; } public boolean isNeedGenDAO() { return needGenDAO; } public void setNeedGenDAO(boolean needGenDAO) { this.needGenDAO = needGenDAO; } }
/** * 示意配置管理,就是负责读取配置文件, * 并把配置文件的内容设置到配置Model中去,是个单例 */ public class ConfigManager { private static ConfigManager manager = null; private static ConfigModel cm = null; private ConfigManager(){ // } public static ConfigManager getInstance(){ if(manager == null){ manager = new ConfigManager(); cm = new ConfigModel(); //读取配置文件,把值设置到ConfigModel中去,这里省略了 } return manager; } /** * 获取配置的数据 * [@return](http://my.oschina.net/u/556800) 配置的数据 */ public ConfigModel getConfigData(){ return cm; } }
/** * 示意生成表现层的模块 */ public class Presentation { public void generate(){ //1:从配置管理里面获取相应的配置信息 ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenPresentation()){ //2:按照要求去生成相应的代码,并保存成文件 System.out.println("正在生成表现层代码文件"); } } } /** * 示意生成逻辑层的模块 */ public class Business { public void generate(){ ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenBusiness()){ System.out.println("正在生成逻辑层代码文件"); } } } /** * 示意生成数据层的模块 */ public class DAO { public void generate(){ ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenDAO()){ System.out.println("正在生成数据层代码文件"); } } }
public class Client { public static void main(String[] args) { //如今没有配置文件,就直接使用默认的配置,一般状况下,三层都应该生成, //也就是说客户端必须对这些模块都有了解,才可以正确使用它们 new Presentation().generate(); new Business().generate(); new DAO().generate(); } }
##1.4 有何问题## 仔细查看上面的实现,会发现其中有一个问题:那就是客户端为了使用生成代码的功能,须要与生成代码子系统内部的多个模块交互
。
这对于客户端而言,是个麻烦,使得客户端不能简单的使用生成代码的功能。并且,若是其中的某个模块发生了变化,还可能会引发客户端也须要跟着变化
。那么如何实现,才能让子系统外部的客户端在使用子系统的时候,既能简单的使用这些子系统内部的模块功能,而又不用客户端去与子系统内的多个模块交互呢?
#2 解决方案# ##2.1 外观模式来解决## 用来解决上述问题的一个合理的解决方案就是外观模式。那么什么是外观模式呢?
这里先对两个词进行一下说明,一个是界面,一个是接口
。
一提到界面,估计不少朋友的第一反应就是图形界面(GUI)
。其实在这里提到的界面,主要指的是从一个组件外部来看这个组件,可以看到什么,这就是这个组件的界面,也就是所说的外观。
好比:你从一个类外部来看这个类,那么这个类的public方法就是这个类的外观,由于你从类外部来看这个类,就能看到这些。
再好比:你从一个模块外部来看这个模块,那么这个模块对外的接口就是这个模块的外观,由于你就只能看到这些接口,其它的模块内部实现的东西是被接口封装隔离了的。
一提到接口,作Java的朋友的第一反应就是interface
。其实在这里提到的接口,主要是指的外部和内部交互的这么一个通道,一般是指的一些方法,能够是类的方法,也能够是interface的方法。也就是说,这里说的接口,并不等价于interface,也有多是一个类。
仔细分析上面的问题,客户端想要操做更简单点,那就根据客户端的须要来给客户端定义一个简单的接口,而后让客户端调用这个接口,剩下的事情就不用客户端管,这样客户端就变得简单了。
固然,这里所说的接口就是客户端和被访问的系统之间的一个通道,并不必定是指Java的interface
。事实上,这里所说的接口,在外观模式里面,一般指的是类,这个类被称为“外观”。
外观模式就是经过引入这么一个外观类,在这个类里面定义客户端想要的简单的方法,而后在这些方法的实现里面,由外观类再去分别调用内部的多个模块来实现功能,从而让客户端变得简单,这样一来,客户端就只须要和外观类交互就能够了。
##2.2 模式结构和说明## 外观模式的结构如图所示:
Facade:定义子系统的多个模块对外的高层接口,一般须要调用内部多个模块,从而把客户的请求代理给适当的子系统对象。 模块:接受Facade对象的委派,真正实现功能,各个模块之间可能有交互。可是请注意,Facade对象知道各个模块,可是各个模块不该该知道Facade对象
。
##2.3 外观模式示例代码## 因为外观模式的结构图过于抽象,所以把它稍稍具体点,假设子系统内有三个模块,分别是AModule、BModule和CModule,它们分别有一个示意的方法,那么此时示例的总体结构如图所示:
/** * A模块的接口 */ public interface AModuleApi { /** * 示意方法,A模块对外的一个功能方法 */ public void testA(); }
public class AModuleImpl implements AModuleApi{ public void testA() { System.out.println("如今在A模块里面操做testA方法"); } }
public interface BModuleApi { public void testB(); } public class BModuleImpl implements BModuleApi{ public void testB() { System.out.println("如今在B模块里面操做testB方法"); } } public interface CModuleApi { public void testC(); } public class CModuleImpl implements CModuleApi{ public void testC() { System.out.println("如今在C模块里面操做testC方法"); } }
/** * 外观对象 */ public class Facade { /** * 示意方法,知足客户须要的功能 */ public void test(){ //在内部实现的时候,可能会调用到内部的多个模块 AModuleApi a = new AModuleImpl(); a.testA(); BModuleApi b = new BModuleImpl(); b.testB(); CModuleApi c = new CModuleImpl(); c.testC(); } }
public class Client { public static void main(String[] args) { //使用Facade new Facade().test(); } }
##2.4 使用外观模式重写示例## 要使用外观模式重写前面的示例,其实很是简单,只要添加一个Facade的对象,而后在里面实现客户端须要的功能就能够了。
/** * 代码生成子系统的外观对象 */ public class Facade { /** * 客户端须要的,一个简单的调用代码生成的功能 */ public void generate(){ new Presentation().generate(); new Business().generate(); new DAO().generate(); } }
其它的定义和实现都没有变化,这里就不去赘述了
看看此时的客户端怎么实现,再也不须要客户端去调用子系统内部的多个模块,直接使用外观对象就能够了,示例代码以下:
public class Client { public static void main(String[] args) { //使用Facade new Facade().generate(); } }
如同上面讲述的例子,Facade类其实至关于A、B、C模块的外观界面,Facade类也被称为A、B、C模块对外的接口
,有了这个Facade类,那么客户端就不须要知道系统内部的实现细节,甚至客户端都不须要知道A、B、C模块的存在,客户端只须要跟Facade类交互就行了,从而更好的实现了客户端和子系统中A、B、C模块的解耦,让客户端更容易的使用系统。
#3 模式讲解# ##3.1 认识外观模式##
外观模式的目的不是给子系统添加新的功能接口,而是为了让外部减小与子系统内多个模块的交互,松散耦合,从而让外部可以更简单的使用子系统。
这点要特别注意,由于外观是看成子系统对外的接口出现的,虽然也能够在这里定义一些子系统没有的功能,但不建议这么作。外观应该是包装已有的功能,它主要负责组合已有功能来实现客户须要,而不是添加新的实现
。
看到Facade的实现,可能有些朋友会说,这不就是把原来在客户端的代码搬到Facade里面了吗?没有什么大变化啊?
没错,说的很对,表面上看就是把客户端的代码搬到Facade里面了,但实质是发生了变化的,请思考:Facade到底位于何处呢?是位于客户端仍是在由A、B、C模块组成的系统这边呢?
答案确定是在系统这边,这有什么不同吗?
固然有了,若是Facade在系统这边,那么它就至关于屏蔽了外部客户端和系统内部模块的交互
,从而把A、B、C模块组合成为一个总体对外,不但方便了客户端的调用,并且封装了系统内部的细节功能,也就是说Facade与各个模块交互的过程已是内部实现了
。这样一来,若是从此调用模块的算法发生了变化,好比变化成要先调用B,而后调用A,那么只须要修改Facade的实现就能够了。
另一个好处,Facade的功能能够被不少个客户端调用,也就是说Facade能够实现功能的共享,也就是实现复用
。一样的调用代码就只用在Facade里面写一次就行了,而不用在多个调用的地方重复写。
还有一个潜在的好处,对使用Facade的人员来讲,Facade大大节省了他们的学习成本,他们只须要了解Facade便可,无需再深刻到子系统内部,去了解每一个模块的细节,也不用和这多个模块交互,从而使得开发简单,学习也容易。
虽然有了外观,若是有须要,外部仍是能够绕开Facade,直接调用某个具体模块的接口,这样就能实现兼顾组合功能和细节功能。好比在客户端就想要使用A模块的功能,那么就不须要使用Facade,能够直接调用A模块的接口。示例代码以下:
public class Client { public static void main(String[] args) { AModuleApi a = new AModuleImpl(); a.testA(); } }
如今的系统是越作越大、愈来愈复杂,对软件的要求也就更高。为了提升系统的可重用性,一般会把一个大的系统分红不少个子系统,再把一个子系统分红不少更小的子系统,一直分下去,分到一个一个小的模块,这样一来,子系统的重用性会获得增强,也更容易对子系统进行定制和使用。
可是这也带来一个问题,若是用户不须要对子系统进行定制,仅仅就是想要使用它们来完成必定的功能,那么使用起来会比较麻烦,须要跟这多个模块交互。
外观对象就能够为用户提供一个简单的、缺省的实现,这个实现对大多数的用户来讲都是已经足够了的。可是外观并不限制那些须要更多定制功能的用户,直接越过外观去访问内部的模块的功能。
##3.2 外观模式的实现##
对于一个子系统而言,外观类不须要不少,一般能够实现成为一个单例
。
也能够直接把外观中的方法实现成为静态的方法,这样就能够不须要建立外观对象的实例而直接就能够调用
,这种实现至关于把外观类当成一个辅助工具类实现。简要的示例代码以下:
public class Facade { private Facade() { } public static void test(){ AModuleApi a = new AModuleImpl(); a.testA(); BModuleApi b = new BModuleImpl(); b.testB(); CModuleApi c = new CModuleImpl(); c.testC(); } }
虽然Facade一般直接实现成为类,可是也能够把Facade实现成为真正的interface,只是这样会增长系统的复杂程度
,由于这样会须要一个Facade的实现,还须要一个来获取Facade接口对象的工厂,此时结构如图所示:
若是把Facade实现成为接口,还附带一个功能,就是可以有选择性的暴露接口方法,尽可能减小模块对子系统外提供的接口方法
。
换句话说,一个模块的接口里面定义的方法能够分红两部分,一部分是给子系统外部使用的,一部分是子系统内部的模块间相互调用时使用的
。有了Facade接口,那么用于子系统内部的接口功能就不用暴露给子系统外部了。
好比,定义以下的A、B、C模块的接口:
public interface AModuleApi { public void a1(); public void a2(); public void a3(); }
同理定义B、C模块的接口:
public interface BModuleApi { //对子系统外部 public void b1(); //子系统内部使用 public void b2(); //子系统内部使用 public void b3(); } public interface CModuleApi { //对子系统外部 public void c1(); //子系统内部使用 public void c2(); //子系统内部使用 public void c3(); }
定义好了各个模块的接口,接下来定义Facade的接口:
public interface FacadeApi { public void a1(); public void b1(); public void c1(); public void test(); }
这样定义Facade的话,外部只须要有Facade接口,就再也不须要其它的接口了,这样就能有效地屏蔽内部的细节,省得客户端去调用A模块的接口时,发现了一些不须要它知道的接口,这会形成“接口污染”
。
好比a二、a3方法就不须要让客户端知道,不然既暴露了内部的细节,又让客户端迷惑。对客户端来讲,他可能还要去思考a二、a3方法用来干什么呢?其实a二、a3方法是对内部模块之间交互的,本来就不是对子系统外部的,因此干脆就不要让客户端知道。
Facade的方法实现中,通常是负责把客户端的请求转发给子系统内部的各个模块进行处理,Facade的方法自己并不进行功能的处理,Facade的方法的实现只是实现一个功能的组合调用
。
固然在Facade中实现一个逻辑处理也并没有不可,可是不建议这样作,这不是Facade的本意,也超出了Facade的边界。
##3.3 外观模式的优缺点##
外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。
外观模式让子系统更加易用,客户端再也不须要了解子系统内部的实现,也不须要跟众多子系统内部的模块进行交互,只须要跟外观交互就能够了,至关于外观类为外部客户端使用子系统提供了一站式服务。
经过合理使用Facade,能够帮助咱们更好的划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把须要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好的隐藏了内部的细节。
##3.4 思考外观模式##
外观模式的本质:封装交互,简化调用。
Facade封装了子系统外部和子系统内多个模块的交互过程,从而简化外部的调用。经过外观,子系统为外部提供一些高层的接口,以方便它们的使用。
外观模式很好的体现了“最少知识原则”。
若是不使用外观模式,客户端一般须要和子系统内部的多个模块交互,也就是说客户端会有不少的朋友,客户端和这些模块之间都有依赖关系,任意一个模块的变更均可能会引发客户端的变更。
使用外观模式事后,客户端只须要和外观类交互,也就是说客户端只有外观类这一个朋友,客户端就不须要去关心子系统内部模块的变更状况了,客户端只是和这个外观类有依赖关系。
这样一来,客户端不但简单,并且这个系统会更有弹性。当系统内部多个模块发生变化的时候,这个变化能够被这个外观类吸取和消化,并不须要影响到客户端,换句话说就是:能够在不影响客户端的状况下,实现系统内部的维护和扩展
。
若是你但愿为一个复杂的子系统提供一个简单接口的时候
,能够考虑使用外观模式,使用外观对象来实现大部分客户须要的功能,从而简化客户的使用;
若是想要让客户程序和抽象类的实现部分松散耦合
,能够考虑使用外观模式,使用外观对象来将这个子系统与它的客户分离开来,从而提升子系统的独立性和可移植性;
若是构建多层结构的系统
,能够考虑使用外观模式,使用外观对象做为每层的入口,这样能够简化层间调用,也能够松散层次之间的依赖关系;
##3.5 相关模式##
这两个模式很是相似,可是有本质的区别。
中介者模式主要用来封装多个对象之间相互的交互,多用在系统内部的多个模块之间
;而外观模式封装的是单向的交互,是从客户端访问系统的调用,没有从系统中来访问客户端的调用。
中介者模式的实现里面,是须要实现具体的交互功能的
;而外观模式的实现里面,通常是组合调用或是转调内部实现的功能,一般外观模式自己并不实现这些功能。
中介者模式的目的主要是松散多个模块之间的耦合,把这些耦合关系所有放到中介者中去实现
;而外观模式的目的是简化客户端的调用,这点和中介者模式也不一样。
一般一个子系统只须要一个外观实例,因此外观模式能够和单例模式组合使用,把Facade类实现成为单例
。固然,也能够跟前面示例的那样,把外观类的构造方法私有化,而后把提供给客户端的方法实现成为静态的。
外观模式的外观类一般须要和系统内部的多个模块交互,每一个模块通常都有本身的接口,因此在外观类的具体实现里面,须要获取这些接口,而后组合这些接口来完成客户端的功能。
那么怎么获取这些接口呢?就能够和抽象工厂一块儿使用,外观类经过抽象工厂来获取所须要的接口,而抽象工厂也能够把模块内部的实现对Facade进行屏蔽
,也就是说Facade也仅仅只是知道它从模块中获取的它须要的功能,模块内部的细节,Facade也不知道了。