前三篇,嘎嘎。
设计模式小结(三)
设计模式小结(二)
设计模式小结(一)
Mediator/Observer:描述:将复杂的组件间交互集中到Mediator中/将复杂组件的交互用Observer - Subject相分离。
动机:软件的复杂之处就在于处理各个组分之间的联系。处理联系一般有两种方式,第一种是将相对独立的点进行有效的划分和隔离,减少软件之间的联系总数,降低复杂度。但是如果这些点之间关系太过复杂,那么划分是无法解决根本复杂度的,这时候,一种可选的方案是干脆将所有的联系封装到一点,让那些本该独立的部分不至于被这些藕断丝连拖累到一起。
对于大多数模式而言,显然前者是比较理想的选择。而Mediator几乎就是23.5个设计模式里面的特例,它的指导思想是将联系及其在软件生命周期中的变化集中到一处以方便管理。
用法:用于维护多个状态的一致性。
Mediator和Observer通常可换用。Mediator更加适用的情况:当操作本身出现强关联性而不是在数据上出现关联性,比方说一些消息的连锁触发。当然也可以把这一触发过程建模成事件后看做是状态的更改而启用Observer模式。
如果需要协调的类是不可更改的,且高度复杂或难于扩充,如第三方GUI控件,那么通常也会使用Mediator而不是Observer。
想用Observer?当然也可以,如果你愿意使用Adapter,Decorator或者Proxy等模式将目标控件封装起来,自然也是可以做到的。
更新操作需要高度集中或者需要高度优化时,需要选择Mediator,这也是紧耦合的擅长之处。
Observer的优势在于可以轻松的用单向连接或轮询的方式完成表现层到模型层的交互,因此对于Web一类有特殊限制的应用,Observer要更加适用一些。
Memento:对于多数系统而言,这一设计几乎是一定可以见到的。Memento的变种颇多,比如序列化/反序列化,持久化/反持久化,实际上都是完成同样的工作。
应用:
Memento自身是需要进行再分层并复用的。通常的Memento分为上下两层,下层与存储设备接驳,负责实际的存储工作。多数语言中的存储流均可以作为这一层中。在这一层需要为存储的物理介质提供一定程度的抽象,这样我们可以不用关心数据流究竟是被存储到内存中还是数据库或者网络服务器上。上层则提供了数据对象的语义,这样可以为客户对象提供一个友善的接口,同时这一抽象可以用于满足其他的需求,如缓存和代理。
Memento有两种典型的存储方式,
一种是为一类对象提供固定的存储模式,这种设计可以最小化数据流量,因而可以获得良好的性能,缺陷在于与对象耦合过于紧密,在面临对象修改的时候灵活性有着很大的不足,
另一种设计是基于查询表的理念。这种方法灵活性较高,可以使同样的数据以多个角度展现,缺点在于会遇到类型转换的问题,存储效率上较差。代码的自描述性弱,需要有文档进行约定。
在实际运用中,Memento模式面临的一大问题在于对内嵌引用的存储问题。如果可以保证存储与读取时被保存对象持有的引用不发生任何可见的变化,那么可以直接保存引用本身(例如C++中的指针地址)。
如果被存储的对象所持有的内嵌引用可以用值进行替换(也就是不共享),那么可以选择将引用也嵌入式的序列化出来。如果需要将被共享的嵌入对象序列化,那么则需要显式的将内嵌的每一个独立的引用标识成一个唯一的句柄。在
序列化时,依次的对象进行序列化,并对序列化的对象用句柄标识。在序列化对象时如果遇到了内嵌的引用,则用它的句柄填充到引用的位置上。在
反序列化时,第一步是先将各个句柄反序列化成对象,填充上所有的值域,并重建句柄表,再利用句柄表将各个对象中的句柄用实际引用进行替换。
这一方法的典型运用包括运行状态的snapshot(例如游戏的读存盘)。
对于固定初始产生固定序列的系统,如伪随机,则可以保存它的初始值,并使用过程化的方法产生序列以恢复现场。但是这里需要注意,
多线程的系统会加大对象恢复的难度,必须要仔细的考虑,例如仅在线程Join的地方,或线程固定的悬挂点上进行序列化工作。
此外,由于Memento经常保存并恢复完整对象,因此可以将Memento与Deepcopy统一设计实现。
例如运用DeepCopy保存当前对象的状态,并在恢复状态时直接用副本替换当前对象,或者将Deepcopy用Memento实现,可以实现对象的远程复制或状态同步。
State/Strategy:State和Strategy的区别很小,他们的差别往往并非来自与程序上的差异而是在观点上的差异性。
个人认为,
和State和Strategy类似的还有Abstract Factory以及Factory Method模式。之所以我将这几个模式绑定在一起进行讨论,本质上是因为他们都将分散在程序各处的分支判断集中到一处进行管理。
如果将这三个模式进行进一步的抽象,可以总结出它们共同的工作方式:
获得上下文->获得前置条件并选择分支->执行分支->设置后置条件->重设上下文。利用这个工作方式,下面简单的分析一下这三个模式各自的异同点以及适用面。
AF工作的理想情况是,上下文的获取、前置条件的设置、分支的选择放在一起完成。也就是说在一个执行期很靠前的地方就完成了产品线的设置,并且在很长一段运行期中不做更改。分支的执行通常比较Lazy,也很分散。后置条件和上下文设置不存在。
在其他工作条件下,
这一模式可能会诱发三个负面问题。第一,是如果产品的创建分散,那么便需要仔细的跟踪程序的执行流程以确定到底哪一套产品线被构造出来;其次,如果产品系列间的区分不是依靠类型信息而是某个内含的状态进行区分,会加大对产品线判断的难度。第三,如果产品系列中对应的产品的构造接口存在这某种语义上的不一致性,或者对应产品本身的接口存在不一致性,都会使得程序的可读性大大下降。
因此Abstract Factory需要尽可能早的确定产品系列,同时产品系列在确定以后尽可能少做修改。如果产品线确定的很迟,那么最好在使用前再进行创建,并将Abstract Factory的创建活动放在它的产品的管理层上(例如XXXManager或XXXCollection这样的)。
State和Strategy的情况较为类似,这个工作流程中,各个工序可能很紧凑(例如FSM,有穷状态机),也有可能很分散。同时由于这一工作流程上下关系紧密,因此各道工序的合并或分离存在多种方案。下面将分析一下State模式。
如果将State的调用方看作State Machine,那么State变化有两类主要的触发者,一类来自于State Machine内,一类是来自于State Machine外的客户代码。实际上真正需要再State Machine内完成状态切换操作,基本上都是State之间的相互切换。就是说State本身是状态切换的触发者,这里简写为State-Triggered Mode(STM)。其他的状态切换的原因基本上都来自于外部,这里简写为Client-Triggered Mode(CTM)。
在设计State时,尽可能避免STM和CTM同时存在。一旦同时存在,由于STM基本上是不可以被放置到State Machine之外的,因此建议将STM和CTM同时封装在State Machine中,客户端通过调用State Machine的对应接口实现CTM。这样可以将State Machine的切换逻辑统一到State中,封装了可能的变化并提供了更高层的语义。
如果只存在CTM,那么State Machine大可以将State直接以getter/setter的形式提供给客户代码访问,简化了客户调用,也能明确客户的职责。
同时,在存在STM的时候,后置条件和上下文的设置,即状态切换既可以放在State中,也可以放在State Machine中。在State中直接切换状态很直接,同时可以在State需要经常修改并改变其后继状态的时候很管用,对于很复杂的状态跳转规则它也可以Work Well,和State Machine的依赖较小,仅仅需要通知State Machine切换到哪个状态上就可以了。同时,一旦状态机中添加或者删除了状态,那么也会使得修改扩散到很多地方。
如果在State Machine中进行状态切换,那么便需要State Machine对State有一定的了解,并且做出正确地决策。如果跳转规则很复杂,那么State Machine就会变得很麻烦,甚至可能导致State Machine变成一盘大的spaghetti。但是对于如果需要经常添加/删除State,或者State跳转规则多但简单的时候,在State Machine中维护状态切换会更方便一些。
好吧,我承认这个和Observer/Mediator一样,两者都很Perfect,如果你的运气够好,你的选择又正确的话。
如果问题本身就很困难,那就请不要过多的苛责设计,No Silver Bullet嘛。Good Luck,或者重新进行需求分析。
Template Method
Template Method是一个有点儿违背面向对象原则的模式。它虽然简化了代码结构,但是由于将一致性的维护分散在了子类和父类两个不同的部分,提高了代码的阅读难度。同时这也是一个实现继承而不是接口继承的例子。面向对象原则这个东西究竟只是原则,实际应用的时候,也是会有一定程度的变通。当然这变通也要付出代价的。虽然我不喜欢Template Method,但是仍然会经常的使用它,因为我遵循Kent Beck大牛的懒惰是程序员的美德这一教导,同时也是因为执迷于Occam剃刀的美感。
Visitor
其实我还真不太明白,如果我需要Visitor的时候,还有什么更好的选择。并且,如果在遍历对象的时候无后效性的话(就是对其它对象的操作和当前对象的操作没什么关系,遍历结果与顺序无关),做起Visitor来,会轻松许多的。
-----------------------------------------------------------------------------------------------------
全文终于完结了。虽然是“泛泛而谈”,但是我觉得这个“泛泛”已经比较详细了。
另外,有些观点可能GOF上面已经有所阐明,不过我相信大部分应该还是没有的。
如果还有什么GOF上说的我重复了的话,那我只能赞叹GOF太博大精深了,以至于我无论看几遍都会对其中的观点有所遗漏。
最后祝大家在Design Pattern中游得愉快。