门面模式,也叫外观模式,是一种比较常用的封装模式,定义如下:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,门面模式提供了一个高层次的接口,使得子系统更易于使用。门面对象是通往外界子系统内部的唯一通道,我们先明确下门面模式的角色:
- Facade门面角色:客户端可以调用这个角色的方法,此角色知晓子系统的所有功能和职责。一般情况下,本角色会将所有客户端发来的请求委派到响应的子系统,也就是该角色没有实际的业务逻辑,只是一个委托类。
- subsystem子系统:可以有一个或多个子系统,每个子系统都不是一个单独的类,而是一个类的集合,子系统并不知道门面的存在,对于子系统而言,门面就是一个客户端而已。
下面看一个门面模式的通用代码示例:
//子系统public class ClassA{ public void doSomethingA(){ }}public class ClassB{ public void doSomethingB(){ }}public class ClassC{ public void doSomethingC(){ }}//门面对象public class Facade{ //被委托的对象 private ClassA a = new ClassA(); private ClassB b = new ClassB(); private ClassC c = new ClassC(); //提供给外部访问的方法 public void methodA(){ this.doSomethingA(); } public void methodB(){ this.doSomethingB(); } public void methodC(){ this.doSomethingC(); }}
门面模式优点:
- 减少系统间依赖
- 提高了灵活性:不管子系统内部如何变化,只要不影响门面对象,可以自由行动。
- 提高了安全性:想让外界访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法,外界就不能访问。
门面模式缺点:
- 门面模式最大缺点就是不符合开闭原则,对修改关闭,对扩展开放,一旦系统投产后发现问题,继承和覆写都不顶用,只能修改门面对象源码,风险较大,需要谨慎对待。
使用场景:
- 为一个复杂的模块或子系统提供一个对外访问的接口
- 子系统相对独立--外界访问只需要黑箱操作即可
- 预防低水平人员带来的风险扩散
注意事项:
1.一个子系统可以有多个门面
一般情况下,一个门面只有一个门面就够了,但在一下情况下可以有多个门面,
- 门面已经庞大到不能忍受的程度:按照功能拆分为多个门面是个不错的原则
- 子系统提供不同的访问路径
2.门面不参与子系统的业务逻辑
以上述通用源代码为例,把门面类上的methodC改一下,让他先调用ClassA的方法,再调用ClassC的方法:
//修改门面public class Facade{ //被委托的对象 private ClassA a = new ClassA(); private ClassB b = new ClassB(); private ClassC c = new ClassC(); //提供给外部访问的方法 public void methodA(){ this.a.doSomethingA(); } public void methodB(){ this.b.doSomethingB(); } //加入业务逻辑 public void methodC(){ this.a.doSomethingA(); this.c.doSomethingC(); }}
上述门面对象中已经参与了业务逻辑,这样设计是非常不靠谱的,为什么呢?因为门面对象只是提供了一个访问子系统的一个路径而已,不应该参与具体的业务逻辑,否则会出现颠倒依赖的问题:子系统必须依赖门面才能访问,这是一个设计的错误!不仅违反单一职责原则,同时也破坏了系统的封装性。那么应该怎么处理呢?建立一个封装类,封装完毕后提供给门面对象。我们先建立一个封装类:
//封装类public class context{ //委托处理 private ClassA a = new ClassA(); private ClassC c = new ClassC(); //业务逻辑、复杂的计算 public void complexMethod(){ this.a.methodA(); this.c.methodC(); }}//门面类public class Facade { //被委托的对象 private ClassA a = new ClassA(); private ClassB b = new ClassB(); private Context context = new Context(); //提供给外部访问的方法 public void methodA(){ this.a.doSomethingA(); } public void methodB(){ this.b.doSomethingB(); } public void methodC(){ this.context.complexMethod(); }}
这样一次封装后,门面对象就不会参与到业务逻辑里了,在门面模式中,门面角色应该是稳定的,他不应该经常变化,一旦系统运行就不应该再被改变,它是子系统对外提供的接口,变来变去还怎么保证其他模块的稳定呢?所有的变化都应该被封装在子系统内部,无论怎么变化,在外界看到还是同一个方法,同一个门面,这才是最好的架构。