一:外观模式诞生的背景
上图很好的介绍了为什么要有外观模式,从图A中我们可以得知客户想组装一台电脑,他自己需要去电子市场逛卖CPU的公司、卖主板的公司,卖内存条的公司,卖显卡的公司……;但是我们也知道有这么一种公司,替你组装电脑,你只需把自己组装电脑的需求说出来,由装机公司去电子市场逛卖CPU的公司,卖主板的公司,卖内存条的公司,卖显卡的公司……。图B其实就是一个很好的很典型的外观模式的例子。
二:外观模式
外观模式可以很好地解决让子系统外部的客户端在使用子系统的时候,既能简单地使用这些子系统内部的模块,而又不用客户端去与子系统内部的多个模块交互。
1:定义
外观模式又称门面模式,它为子系统中的一组接口提供一个一致的界面Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
界面:主要指从一个组件外部来看这个组件,能够看到什么,这就是这个组件的界面,即外观(这里不是我们想的那么简单的GUI)。
接口:主要是指外部和内部交互的一个通道,通常是指一些方法,可以是类方法,也可以使interface的方法。故这里所说的接口并不是我们想的那么简单的interface。
2:应用外观模式解决问题的思路
仔细分析上面的问题,客户端想要操作更简单点,那么就根据客户端的需要定义一个简单的接口,然后让客户端调用这个接口,剩下的事情客户端就不用管了。
当然了这里所说的接口就是客户端和被访问的系统之间的一个通道,并不一定是指Java的interface。它在外观模式中通常指的是类,这个类被称为外观。
外观模式就是通过引入这么一个外观类,在这个类里面定义客户端想要的简单的方法,然后在这些方法的实现里面,由外观类再去分别调用内部的多个模块来实现功能,从而让客户端变得简单。这样一来,客户端就只需要和外观类交互就可以了。
3:外观模式的结构和说明
Facade:定义子系统的多个模块对外的高层接口,通常需要调用内部的多个模块,从而把客户的请求代理给适当的子系统对象。
模块:接受Facade对象的委派,真正实现功能,各个模块之间有可能交互。但是请注意,Facade对象知道各个模块,但是各个模块不应该知道Facade对象。
三:外观模式代码实现
客户组装电脑的小demo代码:
/** * 主板的模块 * @author Peter * */ public interface BoardModuleApi { //卖主板 public void sellBoard(); }
public class BoardModuleApiImpl implements BoardModuleApi { public void sellBoard() { System.out.println("主板"); } }
/** * CPU模块接口 * @author Peter */ public interface CPUModuleApi { //卖CPU public void sellCPU(); }
public class CPUModuleApiImpl implements CPUModuleApi { public void sellCPU() { System.out.println("CPU"); } }
/** * 显卡模块 * @author Peter * */ public interface VideoCardModuleApi { //卖显卡 public void sellVideoCard(); }
public class VideoCardModuleApiImpl implements VideoCardModuleApi { public void sellVideoCard() { System.out.println("显卡"); } }
/** * 外观对象 * @author Peter */ public class Facade { //示意方法,满足客户需要的功能 public void buy(){ //内部实现会调用多个模块 BoardModuleApi board = new BoardModuleApiImpl(); board.sellBoard(); CPUModuleApi cpu = new CPUModuleApiImpl(); cpu.sellCPU(); VideoCardModuleApi videoCard = new VideoCardModuleApiImpl(); videoCard.sellVideoCard(); } }
/** * 客户端 * @author Peter * */ public class Client { public static void main(String[] args) { new Facade().buy(); } }
四:外观模式讲解
1:外观模式的目的
外观模式的目的不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而能让外部更简单的使用子系统。
这点要特别注意,因为外观是当做子系统对外的接口出现的,虽然也可以定义一些子系统没有的功能,但是不建议这么做。外观应该是包装已有的功能,它主要负责组合已有功能来实现客户需要,而不是添加新的实现。
2:使用外观模式和不使用外观模式的区别
最明显的区别就是原来在客户端进行功能组装的代码现在是在Facade里面实现。Facade实现系统的这边,那么它就相当于屏蔽了外部客户端和系统内部模块的直接交互,从而把A、 B、 C模块组合成为一个整体对外,不但方便了客户端的调用,而且封装了系统内部的细节功能。即Facade与各个模块交互的过程已经是内部实现了。这样一来,如果今后调用模块的算法发生了变化,比如变化成要先调用B,然后调用A,那么只需要修改Facade的实现就可以了。
另一个好处是Facade的功能可以被很多个客户端调用,即Facade可以实现功能的共享,即实现复用。同样的调用代码就只用在Facade里面写一次就好了,而不用在多个调用的地方重复写。
还有一个潜在好处,对使用Facade的人员来说,Facade大大节省了他们的学习成本,他们不需要了解A、B、C的实现,也不需要和它们进行交互,只需要了解Facade即可,学习简单,使用简单,开发简单。
3:有外观但是也可以不使用
虽然有Facade,但是如果有需要,外部还是可以绕过Facade,从而直接调用某个模块的接口,这样就能实现兼顾组合功能和细节功能。
4:外观提供了缺省的实现功能
外观对象可以为用户提供一个简单的、缺省的实现,这个实现对大多数的用户来说都是已经足够了的。但是外观并不限制哪些需要更多定制功能的用户,可以直接越过外观去访问内部模块的功能。
5:外观模式调用顺序示意图
五:外观模式的实现
1:Facade的实现
对于一个子系统而言,外观类不需要很多,通常可以实现成为一个单例。也可以直接把外观中的方法实现成为静态方法,这样就不需要创建外观对象的实例而直接调用,这种实现就相当于把外观类当成一个辅助工具类实现。
public interface AModuleApi { public void testA(); }
public class AModuleImpl implements AModuleApi { public void testA() { System.out.println("A Module."); } }
public interface BModuleApi { public void testB(); }
public class BMoudleImpl implements BModuleApi { public void testB() { System.out.println("B Module."); } }
public interface CModuleApi { public void testC(); }
public class CModuleImpl implements CModuleApi { public void testC() { System.out.println("C Module"); } }
public class Facade { private Facade(){} public static void test(){ //实现成为静态方法 AModuleApi a = new AModuleImpl(); a.testA(); BModuleApi b = new BMoudleImpl(); b.testB(); CModuleApi c = new CModuleImpl(); c.testC(); } }
public class Client { public static void main(String[] args) { Facade.test(); } }
2:Facade可以实现成为interface。
缺点:增加了系统的复杂度,因为这样会需要一个对Facade的实现,还需要一个来获取Facade接口对象的工厂。
优点:Facade实现成为接口,还附带一个功能,能够有选择性的暴露接口的方法,尽量减少模块对子系统外提供接口方法。
即:一个模块的接口中定义的方法可以分成两部分,一部分是给子系统外部使用的,一部分是子系统内部的模块间相互调用时使用的。有了Facade接口,那么用于子系统内部的接口功能就不用暴露给子系统的外部了。
public interface AModuleApi { //子系统外部使用 public void testA1(); //子系统内部使用 public void testA2(); //子系统内部使用 public void testA3(); }
public class AModuleImpl implements AModuleApi { public void testA1() { System.out.println("A1 Module"); } public void testA2() { System.out.println("A2 Module"); } public void testA3() { System.out.println("A3 Module"); } }
public interface BModuleApi { //子系统外部使用 public void testB1(); //子系统内部使用 public void testB2(); //子系统内部使用 public void testB3(); }
public class BMoudleImpl implements BModuleApi { public void testB1() { System.out.println("B1 Module"); } public void testB2() { System.out.println("B2 Module"); } public void testB3() { System.out.println("B3 Module"); } }
public interface CModuleApi { //子系统外部使用 public void testC1(); //子系统内部使用 public void testC2(); //子系统内部使用 public void testC3(); }
public class CModuleImpl implements CModuleApi { public void testC1() { System.out.println("C1 Module"); } public void testC2() { System.out.println("C2 Module"); } public void testC3() { System.out.println("C3 Module"); } }
public interface FacadeApi { public void testA1(); public void testB1(); public void testC1(); public void test(); }
public class Facade implements FacadeApi { public void testA1() { AModuleApi a = new AModuleImpl(); a.testA1(); } public void testB1() { BModuleApi b = new BMoudleImpl(); b.testB1(); } public void testC1() { CModuleApi c = new CModuleImpl(); c.testC1(); } public void test() { testA1(); testB1(); testC1(); } }
public class Client { public static void main(String[] args) { new Facade().test(); } }
3:Facade方法的实现
Facade方法实现中,一般是负责把客户端的请求转发给子系统内部的各个模块进行处理,Facade方法本身并不进行功能的处理,Facade的方法实现只是实现一个功能的组合调用。
当然在Facade实现一个逻辑处理也并不是不可以,但是不建议这样做,因为这不是Facade的本意,也超出了Facade的边界。
六:外观模式的优缺点
1:外观模式的优点
(1)松散耦合(外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护);
(2)简单易用(外观模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观交互就可以了,相当于外观类为外部客户端使用子系统提供了一站式服务)
(3)更好地划分访问的层次(通过合理使用Facade,可以帮助我们更好地划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好地隐藏了内部的细节)
2:外观模式的缺点
过多的或者是不太合理的Facade也容易让人迷惑。到底是调用Facade还是直接调用模块,这是一个艰难的choice。
七:小思外观模式
外观模式的本质是:封装交互,简化调用。
外观模式体现了“最少知识原则”。
在希望为一个复杂子系统提供一个简单接口的时候可以考虑使用外观模式,使用外观对象来实现大部分客户需要的功能,从而简化客户的使用;
想要让客户程序和抽象类的实现部分松散耦合可以考虑使用外观模式,使用外观对象来将这个子系统与它的客户分离开,从而提高子系统的独立性和可移植性;
如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,简化层间调用,松散层次之间的依赖关系。