17.门面模式(FacadePattern)
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.门面模式的两个角色
- Facade门面角色:客户端可以调用这个角色的方法。此角色知道子系统的所有功能和责任。一般情况下,本角色会将所有从客户端发来的请求委派到相应的子系统中去,也就是说该角色没有实际的业务逻辑,只是一个委托类。
- subsystem子系统角色:可以同时有一个或多个子系统。每个子系统都不是一个单独的类,而是一个类的集合。子系统不知道门面的存在,对子系统来说门面仅仅是另一个客户端而已。
4.门面模式的优点
- 减少系统的相互依赖:外界只对门面的API产生依赖,而对子系统内部完全解耦。
- 提高了灵活性:依赖减少了,灵活性自然提高了。不管子系统内部如何变化,只要不影响门面对象,任你*活动。
- 提高安全性:想让你访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法,你休想访问。
5.门面模式的缺点
门面模式的最大缺点就是不符合开闭原则,对修改关闭,对扩展开放,看看我们的门面对象,它可是重中之重,一旦系统投产后发现有一个小错误,怎么解决?完全遵照开闭原则根本无法解决。继承和覆盖都顶不上用。唯一能做的就是修改门面角色的代码,这个风险很大。
6.门面模式的注意事项
6.1什么时候一个子系统需要多个门面
- 门面已经庞大到不能忍受的程度:当一个类的代码有好几千行的时候,我觉得你很好理解。
- 子系统可以提供不同的访问路径:上面的通用代码中Facade类对外暴露了ClassA、ClassB、ClassC的所有API,但是这些API我可能只想暴露给X客户端,至于给Y客户端我只想暴露ClassB的API,怎么实现?请看下面代码:
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(); } }
通过这样一次封装后,门面对象又不参与业务了,在门面模式中,门面角色应该是稳定的,它不应该经常变化,一个系统一旦投入运行,它就不应该被改变,它是一个系统对外的接口,你经常变化怎么保证其他模块的稳定运行呢?但是,业务逻辑是经常改变的,我们已经把它封装在子系统内部,无论你如何变化,对外界的访问者来说,都还是同一个门面,同样的方法-这才是架构师最希望看到的结构。
上一篇: 门面模式
下一篇: 设计模式(七)——门面模式