1.定义
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。
门面模式提供一个高层次的接口,使得子系统更易于使用。
门面模式注重“统一的对象”,也就是提供一个访问子系统的接口,除了这个接口不允许有任何访问子系统的行为发生。
门面对象是外界访问子系统内部的唯一通道,不管子系统内部是多么杂乱无章,只要有门面在,就可以做到“金玉其外败絮其中”。
2.门面模式的使用场景
门面模式很简单,无非就是对外只提供一个门面暴露外界需要的API,请看下面的通用代码
package _17FacadePattern; /** * 子系统中的A业务 */ public class ClassA { public void doSomethingA() { } }
package _17FacadePattern; /** * 子系统中的B业务 */ public class ClassB { public void doSomethingB() { } }
package _17FacadePattern; /** * 子系统中的门面1,只暴露外界需要的API */ public class Facade { private ClassA classA = new ClassA(); private ClassB classB = new ClassB(); // 对外暴露的API public void methodA() { classA.doSomethingA(); } public void methodB() { classB.doSomethingB(); } }
3.门面模式的两个角色
4.门面模式的优点
5.门面模式的缺点
门面模式的最大缺点就是不符合开闭原则,对修改关闭,对扩展开放,看看我们的门面对象,它可是重中之重,一旦系统投产后发现有一个小错误,怎么解决?完全遵照开闭原则根本无法解决。继承和覆盖都顶不上用。唯一能做的就是修改门面角色的代码,这个风险很大。
6.门面模式的注意事项
6.1什么时候一个子系统需要多个门面
package _17FacadePattern; /** * 子系统中的门面2,对外暴露ClassB的API */ public class Facade2 { private Facade facade = new Facade(); // 对外暴露ClassB的API public void methodB() { facade.methodB(); } }
增加的门面很简单,委托给了已经存在的门面对象Facade进行处理,为什么要是有委托而不再编写一个委托到子系统的方法呢?那是因为在面向对象编程中,尽量保持相同的代码只写一遍,避免以后到处修改相似代码的悲剧。
6.2门面不参与子系统的内的业务逻辑
这是什么意思呢,请看下面代码:
package _17FacadePattern; /** * 子系统中的门面3,门面中包含了业务 */ public class Facade3 { private ClassA classA = new ClassA(); private ClassB classB = new ClassB(); // 对外暴露的API public void methodA() { classA.doSomethingA(); } public void methodB() { classA.doSomethingA(); classB.doSomethingB(); } }
因为某个需求更改,我们在门面的methodC里面调用了另一个方法。其实这样的设计是非常不靠谱的,为什么呢?因为你已经让门面对象参与了业务逻辑,门面对象只是提供一个访问子系统的路径而已,它不应该也不能参与具体的业务逻辑,否则就会产生一个倒依赖的问题,子系统必须依赖门面才能被访问,这是设计上的一个严重错误,不仅违背了单一职责原则,同时也破坏了系统的封装性。
好吧,说了这么多,让我们看看应该怎么改:
先将methodC的逻辑封装到另一个类中
package _17FacadePattern; /** * 业务的封装类 */ public class Context { private ClassA classA = new ClassA(); private ClassB classB = new ClassB(); // 对外暴露的API public void methodB() { classA.doSomethingA(); classB.doSomethingB(); } }
然后门面中调用封装类:
package _17FacadePattern; /** * 子系统中的门面4,将3中包含的业务逻辑封装到一个业务类中 */ public class Facade4 { private ClassA classA = new ClassA(); private Context context = new Context(); // 对外暴露的API public void methodA() { classA.doSomethingA(); } public void methodB() { context.methodB(); } }
通过这样一次封装后,门面对象又不参与业务了,在门面模式中,门面角色应该是稳定的,它不应该经常变化,一个系统一旦投入运行,它就不应该被改变,它是一个系统对外的接口,你经常变化怎么保证其他模块的稳定运行呢?但是,业务逻辑是经常改变的,我们已经把它封装在子系统内部,无论你如何变化,对外界的访问者来说,都还是同一个门面,同样的方法-这才是架构师最希望看到的结构。