活用设计模式

 

一、 设计模式的隐喻
     武功套路是习武的门径。新手要一招一式地练习套路,烂熟于心之后,
熟能生巧,在实战之中即可见招拆招、运用自如——此时习武之人已从
“新手”成长为“好手”。“高手”则没有套路,实战之中只有自然反应,
然而一招一式浑然天成、恰到好处,似有似无、无中生有。“高手”之上
还有“高高手”,他们达到的境界非我等凭借金氏武侠小说可以揣测。
    设计模式之于设计,好比套路之于武术。“新手”要一个接一个地学习
模式,“好手”能够活用模式,“高手”则没有模式。
    设计模式的“内功”是面向对象的基本原则。这些原则是“神”,模式
是“形”。高手拼的是“内功”,对面向对象基本原则有了深刻的领悟,才
能用好设计模式,避免“走火入魔”。
    一般在设计模式著作的前几章都会介绍面向对象的基本原则,这几章非
常重要。学通了这几章,后面的模式就不过如此了。学完了设计模式,也最
好翻过头来重新看看这几章,保证会有新的领悟。

 

二、 为什么使用设计模式
    对任何设计都可以凭主观(对设计很难做出客观评价)判断得出它是一
个好的设计,还是一个坏的设计。使用设计模式是为了避免坏的设计。
Martin叔叔在他的著作《敏捷软件开发 原则、模式与实践》中描述了拙劣
设计的症状:

僵化性(Rigidity):设计难以改变。
脆弱性(Fragility):设计易于遭到破坏。
牢固性(Immobility):设计难以重用。
粘滞性(Viscosity):难以做正确的事情。
不必要的复杂性(Needless Complexity):过分设计。
不必要的重复(Needless Repetition):过多的重复。
晦涩性(Opacity):混乱的表达。

 

三、 什么时候使用设计模式
    Martin叔叔的书中有段话:
  
    在学习它们(设计原则和模式)的时候,请记住,敏捷开发人员不会对
一个庞大的预先设计应用那些原则和模式。相反,这些原则和模式被应用在
一次次的迭代中,力图使代码以及代码所表达的设计保持干净。
  
    在这段容易被读者忽略的文字中,我体会到这样几层含义:

    .代码是设计(这是Martin叔叔强调的一个观点,这个观点可以参考
     《敏捷软件开发 原则、模式与实践》一书的附录D);
    .设计模式是为了使设计适应变化;
    .设计模式是重构的工具;
    .设计一开始就要保持干净、简单,以后仍然要保持干净、简单;
    .不能过度使用设计模式。

    使用设计模式的目的是为了适应未来的变化,变化之所以存在是因为它
的不可预知性——如果可以预知,则不能称其为变化。如何判断哪些需求可
能变化,哪些需求可能不变,并且在最大程度上保持设计的干净、简单,这
是些工艺问题,而不是工程问题。既然是工艺问题,那么就只能给出原则,
不能给出标准。使用设计模式的大体原则可能是:对未来极有可能发生变化
的问题给出最简单、修改成本最低的解。
  
四、 避免过度使用设计模式
    易维护的程序首先要易理解,这一点远甚于其他。在易理解的代码上才
好维护。过分地使用设计模式会增加程序的复杂性和晦涩性,让程序不易理
解,从而降低了程序的易维护性。
    Switch语句曾经遭致诟病,许多重构的例子就是拿Switch开刀。我认为
Switch语句是高效的语句,可以写出极优雅、简单的代码。在很多情况下,
直接使用Switch语句比把它拆成若干个Class更“干净”。
    再比如,有一段四百多行的代码负责整个系统的调度,如果未来的变化
仅仅是修改这四百行代码而不会大量添加代码,那么把这四百多行代码集中
在一个函数里面,比将它拆分成十来个Class更加容易维护。

五、 讨论几个具体的模式

1、 创建模式(Creational Pattern)
    工厂(Factory Method)模式是常用的模式。工厂模式的应用情景明确,
设计思想简单。从使用多态到只用一个静态方法,工厂模式的变化形式有很
多。我习惯简单地使用工厂模式,也就是使用只有静态方法的工厂模式。下
面的工厂模式代码简单、干净:
  
  MyFactory.GetClassInstance().DoFunction();
  
    类厂并不承载业务逻辑,需求变化对类厂的影响通常很小。因此使用重
量级的工厂模式往往并不划算。一组包含层次关系的重量级的工厂类,可能
意味着过度设计。
   单例(Singleton)模式和工厂模式关系密切。从实现的角度讲,单例模
式是工厂模式的一个特例,但是两个模式的应用情景不同,因此它们属于不
同的模式。
   抽象工厂(Abstract Factory)模式是工厂模式的推广。抽象工厂模式
的应用情景更加特殊和严格。在一个使用抽象工厂的设计中,如果未来发生
不同产品族各自演化的情形,那么抽象工厂模式就可能崩溃了。在实际应用
中,不同产品族各自演化,最终分道扬镳的情形是有的,用户提出这样的需
求的确让人“触目惊心”。在使用抽象工厂模式之前,一定要保证从现在到
未来都能够用一致的方式使用这些产品族。
   将工厂模式稍加变化可以得到建造(Builder)模式。工厂模式的“加工
工艺”是隐藏的,而建造模式的“加工工艺”是暴露的。这点不同,使建造
模式在更加灵活的同时也有失优雅。

2、 模板模式(Template Method)和策略(Strategy)模式
   模板模式和策略模式的应用情景类似,但实现方式不同,前者使用继承,
后者使用委托。
   模板模式有可能是最“古老”的模式之一,在使用面向对象技术的早期,
“继承”大行其道,很多设计人员可能不自觉地使用过模板模式。模板模式
的缺点是把具体实现和通用算法紧密地耦合起来,使得具体实现只能被一个
通用算法操纵。然而在继承关系中,父类的信息可以更多地暴露给子类,这
种(违背面向对象设计原则的)微妙的沟通在一些特定应用中显得更加灵活
和方便。
   策略模式是委托的经典用法。策略模式消除了通用算法和具体实现的耦
合,使得具体实现可以被多个通用算法操纵。策略模式也增加了类层次,比
模板模式复杂。
   模板模式和策略模式通常可以互相替换。它们都像试卷,模板模式是填
空题,策略模式是选择题。

3、 简化问题的模式
    门面(Facade)模式把一组复杂的接口隐藏在一个简单且特定的接口后
面。
    调停者(Mediator)模式把对象之间的引用关系包装在一个特定的容器
里面。
   组合(Composite)模式描述了整体与部分的结构关系,并且允许用一致
的方式处理这个结构。
   上面几个模式对使用者而言,都在一定程度上起到了简化问题的作用。

4、 扩展功能的模式
   访问者(Visitor)模式和装饰(Decorator)模式都可以在不改变现有
类结构的基础上,动态地增加功能。
   访问者模式把现有类结构上的对象“分配”到一个名为访问者的类中,
在访问者的相应方法中配置对象、改变对象或扩展功能。
   装饰模式把现有类结构上的对象“注入”一个装饰类中,在装饰类中扩
展它的功能。
   访问者模式和装饰模式在实际效果上是不同的。访问者模式可以把对象
分配到相应的方法里,从而对每个对象分别进行加工或扩展。而装饰模式只
能用一致的方式对所有的被装饰对象进行加工或扩展,要想实现不同的加工
或扩展,只能增加新的装饰类。
   过多的“装饰类”有可能使业务逻辑分散,并且使程序结构复杂。针对
每一个具体的派生类,“访问类”都要有一个对应的方法,增加派生类的时
候也要增加访问类的方法。扩展功能的需求是经常发生的,是否有必要使用
上述模式则值得再三考虑。

5、 其他常用的模式
   桥梁(Bridge)模式。Class是封装了行为和属性的容器,然而Class的
一组行为可能独立演化,这时最直接的想法是使用继承,把各不相同的行为
封装在不同的子类里。桥梁模式从另外的角度解决了这个问题。桥梁模式把
独立演化的行为封装在另外一个类体系里,与原来的类体系分别独立演化,
两个类体系在抽象层次是“使用”关系。在很多OO教材里面用Shape类封装
属性和Draw方法,在桥梁模式里,“形状”和“画笔”是两组独立演化的类
体系,在抽象层次,“形状”使用“画笔”绘制自己。
适配器(Adapter)模式是常用模式,它比较简单,有时和其他的模式配合
使用。
   命令(Command)模式被Martin称为“最简单、最优雅的模式之一”。命
令模式的魅力在于它为每个类“培训”出了相同的技能,经过“培训”的类
“柔性”更强,能够产生不可思议的能力。

6、 不太需要的模式
   观察者(Observer)模式。Java和C# 都实现了观察者模式。
   迭代子(Iterator)模式。在Java和C#语言里,可以用聚集类代替。
   备忘录(Memento)模式。可以用Class的序列化能力代替。
   责任链(Chain of Responsibility)模式。可以用其他的方式替代,
例如观察者模式、语言本身提供的消息机制等。
   解释器(Interpreter)模式。个人认为,它是个算法,不是模式。
   代理(Proxy)模式。如果在一开始就知道某些底层策略一定会被替换掉,
那么使用代理来隔离这些策略还是有必要的。否则,几乎没有使用的必要。

原文地址:https://www.cnblogs.com/fhuafeng/p/1630051.html