王大虎

王大虎:23.4 门面模式的注意事项

王大虎

23.4.1 一个子系统可以有多个门面

 

一般情况下,一个子系统只要有一个门面足够了,在什么情况下一个子系统有多个门面呢?以下列举了几个。

·门面已经庞大到不能忍受的程度

比如一个纯洁的门面对象已经超过了200行的代码,虽然都是非常简单的委托操作,也建议拆分成多个门面,否则会给以后的维护和扩展带来不必要的麻烦。那怎么拆分呢?按照功能拆分是一个非常好的原则,比如一个数据库操作的门面可以拆分为查询门面、删除门面、更新门面等。

·子系统可以提供不同访问路径

我们以门面模式的通用源代码为例。ClassA、ClassB、ClassC是一个子系统的中3个对象,现在有两个不同的高层模块来访问该子系统,模块一可以完整的访问所有业务逻辑,也就是通用代码中的Facade类,它是子系统的信任模块;而模块二属于受限访问对象,只能访问methodB方法,那该如何处理呢?在这种情况下,就需要建立两个门面以供不同的高层模块来访问,在原有的通用源码上增加一个新的门面即可,如代码清单23-10所示。

代码清单23-10 新增门面

 

* * *

 

public class Facade2 { //引用原有的门面 private Facade facade = new Facade(); //对外提供唯一的访问子系统的方法 public void methodB(){ this.facade.methodB(); } }

 

王大虎

 

增加的门面非常简单,委托给了已经存在的门面对象Facade进行处理,为什么要使用委托而不再编写一个委托到子系统的方法呢?那是因为在面向对象的编程中,尽量保持相同的代码只编写一遍,避免以后到处修改相似代码的悲剧。

 

 

23.4.2 门面不参与子系统内的业务逻辑

 

我们这节的标题是什么意思呢?我们举一个例子来说明,还是以通用源代码为例。我们把门面上的methodC上的逻辑修改一下,它必须先调用ClassA的doSomethingA方法,然后再调用ClassC的doSomethingC方法,如代码清单23-11所示。

代码清单23-11 修改门面

 

* * *

 

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(); } }

 

* * *

 

还是非常简单,只是在methodC方法中增加了doSomethingA()方法的调用,可以这样做吗?我相信大部分读者都说可以这样做,而且已经在实际系统开发中这样使用了,我今天告诉各位,这样设计是非常不靠谱的,为什么呢?因为你已经让门面对象参与了业务逻辑,门面对象只是提供一个访问子系统的一个路径而已,它不应该也不能参与具体的业务逻辑,否则就会产生一个倒依赖的问题:子系统必须依赖门面才能被访问,这是设计上一个严重错误,不仅违反了单一职责原则,同时也破坏了系统的封装性。

说了这么多,那对于这种情况该怎么处理呢?建立一个封装类,封装完毕后提供给门面对象。我们先建立一个封装类,如代码清单23-12所示。

代码清单23-12 封装类

王大虎
王大虎

王大虎

 

public class Context { //委托处理 private ClassA a = new ClassA(); private ClassC c = new ClassC(); //复杂的计算 public void complexMethod(){ this.a.doSomethingA(); this.c.doSomethingC(); } }

 

* * *

 

该封装类的作用就是产生一个业务规则complexMethod,并且它的生存环境是在子系统内,仅仅依赖两个相关的对象,门面对象通过对它的访问完成一个复杂的业务逻辑,如代码清单23-13所示。

代码清单23-13 门面类

 

* * *

 

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(); } }

 

* * *

 

通过这样一次封装后,门面对象又不参与业务逻辑了,在门面模式中,门面角色应该是稳定,它不应该经常变化,一个系统一旦投入运行它就不应该被改变,它是一个系统对外的接口,你变来变去还怎么保证其他模块的稳定运行呢?但是,业务逻辑是会经常变化的,我们已经把它的变化封装在子系统内部,无论你如何变化,对外界的访问者来说,都还是同一个门面,同样的方法——这才是架构师最希望看到的结构。

 

王大虎

23.5 最佳实践

 

门面模式是一个很好的封装方法,一个子系统比较复杂时,比如算法或者业务比较复杂,就可以封装出一个或多个门面出来,项目的结构简单,而且扩展性非常好。还有,对于一个较大项目,为了避免人员带来的风险,也可以使用门面模式,技术水平比较差的成员,尽量安排独立的模块,然后把他写的程序封装到一个门面里,尽量让其他项目成员不用看到这些人的代码,看也看不懂,我也遇到过一个“高人”写的代码,private方法、构造函数、常量基本都不用,你要一个public方法,好,一个类里就一个public方法,所有代码都在里面,然后你就看吧,一大坨程序,看着就能把人逼疯。使用门面模式后,对门面进行单元测试,约束项目成员的代码质量,对项目整体质量的提升也是一个比较好的帮助。

 

 

第24章 备忘录模式

 

24.1 如此追女孩子,你还不乐

 

大家有没有看过尼古拉斯·凯奇主演的《Next》(中文译名为《预见未来》)?尼古拉斯·凯奇饰演一个可以预视并且扭转未来的人,其中有一个情节很是让人心动——男女主角见面的那段情节:Cris Johnson(尼古拉斯·凯奇饰演)坐在咖啡吧台前,看着离自己近在咫尺的Callie Ferris(朱莉安·摩尔饰演),计划着怎么认识这个命中注定的女人,看Cris Johnson如何利用自己的特异功能:

·Cris Johnson端着一杯咖啡走过去,说“你好,可以认识你吗?”被拒绝,恢复到坐在咖啡吧台前的状态。

·走过去询问是否可以搭车,被拒绝,恢复原状。

·帮助解决困境,被拒绝,恢复原状。

·采用嬉皮士的方式解决困境,被拒绝,恢复原状。

·帮助解决困境,被打伤,装可怜,Callie Ferris怜惜,于是乎相识了。

看看这是一件多么幸福的事情,追求一个女生可以多次反复地实验,直到找到好的方法和途径为止,这估计是大多数男生都希望获得的特异功能。想想看,看到一个心仪的女生,我们若反复尝试,总会有一个方法打动她的,多美好的一件事。现在我们还得回到现实生活,我们来分析一下类似事情的经过:

·复制一个当前状态,保留下来,这个状态就是等会儿搭讪女孩子失败后要恢复的状态,你不恢复原始状态,这不就露馅儿了吗?

·每次试探性尝试失败后,都必须恢复到这个原始状态。

·N次试探总有一次成功吧,成功以后即可走成功路线。

想想看,我们这里的场景中最重要的是哪一块?对的,是原始状态的保留和恢复这块,如何保留一个原始,如何恢复一个原始状态才是最重要的,那想想看,我们应该怎么实现呢?很简单呀,我们可以定义一个中间变量,保留这个原始状态。我们先看看类图,如图24-1所示。

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注