设计模式学习

来源:互联网 发布:关于远离网络的演讲稿 编辑:程序博客网 时间:2024/06/05 17:33

设计模式学习

    by:陈珍敬

说明:这些资料仅仅是对设计模式的一些总结,没有设计模式的相关知识,是很难看懂的。即使看懂了这些,也只是说明,理解了模式的基本思想。想要学好设计模式,还是建议好好看文后所列的参考书籍和推荐书籍。

这些总结有不少是根据自己的理解写成的,或许不是正确的。如果您有不同的看法,请告知作者,谢谢!

欢迎传阅,但是请勿随意修改或Copy。

设计模式简介

每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。                                      -- Christopher Alexander

设计模式描述了软件设计过程中某一类常见问题的一般性的解决方案。面向对象设计模式描述了面向对象设计过程中,特定场景下,类(抽象类之间,抽象类和派生类)之间或者相互通信的对象之间常见的组织关系。

对象是什么?

从概念层面讲,对象是某种拥有责任的抽象
从规格层面讲,对象是一系列可以被其他对象使用的公共接口
从语言实现层面来看,对象封装了代码和数据。

 

从设计原则到设计模式

针对接口编程,而不是针对实现编程

客户无需知道所使用对象的特定类型,只需要知道对象拥有客户所期望的接口。

优先使用对象组合,而不是类继承

类继承通常为白箱复用,对象组合通常为黑箱复用。继承在某种程度上破坏了封装性,子类和父类耦合度高;而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。

封装变化点

使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。

关于类设计的五项原则,它们是:

  • SRP,单一职责原则,一个类应该有且只有一个改变的理由。
  • OCP,开放封闭原则,你应该能够不用修改原有类就能扩展一个类的行为。类模块应该是可扩展的,但是不可修改(对扩展开放,对更改封闭).
  • LSP,Liskov替换原则,派生类要与其基类自相容. 子类必须能够替换它们的基类.
  • DIP,依赖倒置原则,依赖于抽象而不是实现。高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于实现细节,实现细节应该依赖于抽象。  
  • ISP,接口隔离原则,客户只要关注它们所需的接口。不应该强迫客户程序依赖于它们不用的方法。

 

模式与设计的关系

每个模式都描述了某个特定场景中一个特定问题的约束因素/动机和关系,并为我们提供一种解决这些问题的优雅方案.换句话说,模式仅仅是描述了特定场景下的关系和约束因素.正因如此,模式本身并不是最重要的. 特定场景下的关系和约束因素才是最真实的,模式仅仅是提供了一组描述这些关系的一组词汇,提供了一套解决这些关系的优雅方式而已.

 

在软件设计中,模式是随特定场景下的关系和约束因素而定的.也就是说对所要解决问题的分析和理解是使用模式的必要条件.只有清楚了特定场景下的关系和约束因素,才能很好的选择解决问题的方法和适用的模式.

 

特定模式描述的是问题中概念(抽象类)之间的关系,比如所有的行为模式,bridge模式等;或者是抽象类和派生类之间的关系,比如Proxy, Composite, Decorator,抑或是新类和原有类之间的关系,Adaptor, Facade.这些关系不是显而易见的,这是分析的结果.没有从概念视角对问题的分析,这些关系是不能凸现出来的.因此,使用模式应该是建立在共性与可变形分析,分析矩阵等方法之上的.概念视角层次上的分析,往往考虑的是抽象类之间的关系,因此这是所采用的模式一般是行为模式,bridge模式等.描述抽象类和派生类之间的关系的模式一般只有等到实现层次时,才考虑引入.这种分层次的设计,有助于简化设计过程,忽略不必要的次要因素,产生更好的高层设计.

 

根据上述观点,虽然模式经常是组合使用,模式的使用顺序是有先后之分的.在高层设计会采用一些高层的主模式,在代码实现级别上,也会再次引入其他合适的模式.个人觉得行为模式、BridgeFacade等模式是最可能以主模式的形式出现的.

 

模式与继承

(这里仅针对静态语言,在动态语言中,多态并非通过继承才能实现)

封装/继承/多态都是面向对象的基本概念.前面已经讲过,封装变化是模式的核心思想之一. 同时,从抽象类和派生类的角度看,继承和多态使得抽象类能够封装派生类的类型.显然,继承和多态使类封装变化成为可能.因此,在设计模式中,继承基本上是随处可见的.

从模式的角度看,模式是面向对象复用的基础元素.采用模式可以使设计的软件更加灵活,更容易扩展,更容易维护.这也正是OCP(开放封闭原则)所强调的.要实现这样的一个目标,那么继承也是必不可少的.继承使派生类之间可以互相替换,因此也就封装了抽象类背后的变化.

一般来说使用模式是有代价的,模式虽然有其自身的优越性,但是只有在问题本身有一定的复杂性时,采用模式来简化这些复杂性才是有意义的.然而,不能忽略的是,模式本身(所涉及的相互交互的类)是有一定复杂性的.每个模式中相互交互的类之间,可以说是紧密耦合在一起的.这些类基本上是不可分的,它们本身就是作为一个整体而存在.同时这些类背后的继承关系在使模式灵活的同时也增加了复杂性.这都是使用模式时需要考虑的因素.切记:模式中的类是作为一个整体而存在,它们是紧密耦合的关系;模式描述了这些类之间的关系和约束因素,使我们容易交流,但同时要清楚实现时是对这些类和这些交互关系的再现.

 

 

模式的分类

模式依据其目的可分为创建型(Creational)、结构型(Structural)、或行为型(Behavioral)三种。创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分配职责进行描述。

第二是范围准则,指定模式主要是用于类还是用于对象。类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了。对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性。从某种意义上来说,几乎所有模式都使用继承机制,所以“类模式”只指那些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴。

    分类如下:

    说明:

创建型类模式对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一

个对象中。结构型类模式使用继承机制来组合类,而结构型对象模式则描述了对象的组装方

式。行为型类模式使用继承描述算法和控制流,而行为型对象模式则描述一组对象怎样协作

完成单个对象所无法完成的任务。

还有其他组织模式的方式。有些模式经常会被绑在一起使用,例如,Composite常和Iterato rVisitor一起使用;有些模式是可替代的,例如, Prototype常用来替代Abstract Factory;有些模式尽管使用意图不同,但产生的设计结果是很相似的,例如, CompositeDecorator的结构图是相似的。

 

设计模式与封装变化

设计模式可以确保系统能以特定方式变化(这很大程度是一种猜测),从而帮助你避免重新设计系统。每一个设计模式允许系统结构的某个方面的变化独立于其他方面,这样产生的系统对于某一种特殊变化将更健壮。

下面阐述了一些导致重新设计的一般原因,以及解决这些问题的设计模式:

1) 通过显式地指定一个类来创建对象在创建对象时指定类名将使你受特定实现的约束, 而不是特定接口的约束。这会使未来的变化更复杂。要避免这种情况,应该间接地创建对象

设计模式: Abstract FactoryFactory MethodPrototype

2) 对特殊操作的依赖当你为请求指定一个特殊的操作时,完成该请求的方式就固定下来了。为避免把请求代码写死,你将可以在编译时刻或运行时刻很方便地改变响应请求的方法。

设计模式: Chain of Responsibility (5.1)Command ( 5 . 2 )

3) 对硬件和软件平台的依赖外部的操作系统接口和应用编程接口(API)在不同的软硬件平台上是不同的。依赖于特定平台的软件将很难移植到其他平台上,甚至都很难跟上本地平台的更新。所以设计系统时限制其平台相关性就很重要了。

设计模式: Abstract Factory(3.1)Bridge ( 4 . 2 )

4) 对对象表示或实现的依赖知道对象怎样表示、保存、定位或实现的客户在对象发生变化时可能也需要变化。对客户隐藏这些信息能阻止连锁变化。

设计模式: Abstract Factory(3.1)Bridge ( 4 . 2 )Memento ( 5 . 6 )Proxy ( 4 . 7 )

5) 算法依赖算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此有可能发生变化的算法应该被孤立起来。

设计模式: Builder ( 3 . 2 )Iterator ( 5 . 4 )Strategy ( 5 . 9 )Template Method(5.10)

Visitor ( 5 . 11 )

6)紧耦合的类很难独立地被复用,因为它们是互相依赖的。紧耦合产生单块的系统,要改变或删掉一个类,你必须理解和改变其他许多类。这样的系统是一个很难学习、移植和维护的密集体。

松散耦合提高了一个类本身被复用的可能性,并且系统更易于学习、移植、修改和扩展。

设计模式使用抽象耦合分层技术来提高系统的松散耦合性。

设计模式: Abstract Factory(3.1) Command ( 5 . 2 )Facade ( 4 . 5 )Mediator ( 5 . 5 )

Observer(5.7) Chain of Responsibility(5.1)

7) 通过生成子类来扩充功能通常很难通过定义子类来定制对象。每一个新类都有固定的实现开销(初始化、终止处理等)。定义子类还需要对父类有深入的了解。如,重定义一个操作可能需要重定义其他操作。一个被重定义的操作可能需要调用继承下来的操作。并且子类方法会导致类爆炸,因为即使对于一个简单的扩充,你也不得不引入许多新的子类。(本质: 继承应该仅仅封装一个变化点)

一般的对象组合技术和具体的委托技术,是继承之外组合对象行为的另一种灵活方法。新的功能可以通过以新的方式组合已有对象,而不是通过定义已存在类的子类的方式加到应用中去。另一方面,过多使用对象组合会使设计难于理解。许多设计模式产生的设计中,你可以定义一个子类,且将它的实例和已存在实例进行组合来引入定制的功能。

设计模式:Bridge ( 4 . 2 )Chain of Responsibility(5.1)Composite ( 4 . 3 )Decorator ( 4 . 4 )

Observer (5.7)Strategy ( 5 . 9 )

8) 不能方便地对类进行修改有时你不得不改变一个难以修改的类。也许你需要源代码而又没有(对于商业类库就有这种情况),或者可能对类的任何改变会要求修改许多已存在的其他子类。设计模式提供在这些情况下对类进行修改的方法。

设计模式: Adapter ( 4 . 1 )Decorator ( 4 . 4 )Visitor ( 5 . 11 )

 

创建型模式

创建型模式抽象了实例化过程。它们帮助一个系统独立于如何创建、组合、管理和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。

随着系统演化得越来越依赖于对象复合而不是类继承,创建型模式变得更为重要。当这种情况发生时,重心从对一组固定行为的硬编码(hard-coding)转移为定义一个较小的基本行为集,这些行为可以被组合成任意数目的更复杂的行为。这样创建有特定行为的对象要求的不仅仅是实例化一个类。

在这些模式中有两个不断出现的主旋律。第一,它们都将关于该系统使用哪些具体的类的信息封装起来。第二,它们隐藏了这些类的实例是如何被创建和放在一起。整个系统关于这些对象所知道的是由抽象类所定义的接口。因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以及何时创建这些方面给予你很大的灵活性。它们允许你用结构和功能差别很大的“产品”对象配置一个系统。配置可以是静态的(即在编译时指定),也可以是动态的(在运行时)。

 

Abstract Factory

意图: 提供一个创建一系列相关相互依赖对象的接口,而无需指定它们具体的类。

动机: 封装一系列相关相互依赖对象的创建,隐藏了相关对象创建过程的选取组合问题,减少错误组合的可能性.

适用性: 常用于不同软件构建环境(平台,不同的软件需求配置等)下相关对象的创建.

结构: 一般使用继承来封装不同软件构建环境.

优点: 减少对象错误组合使用的可能性.

 

Builder

意图: 将一个复杂对象(一般内部含有多个其他对象)的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

动机: 封装复杂对象或具有多种组合的对象的创建过程.比如提供不同的接口表示同一个对象不同的构建过程.

适用性: 常用于封装具有可变构建过程的对象.

结构: 提供不同的接口封装不同的构建过程;提供较小粒度的接口,封装构建过程中对象间的交互关系,让客户通过提供的接口自己合成创建过程.

优点: 要么封装整个对象的构建,要么封装了创建过程中对象间的交互关系.

组合模式: 经常用于封装Composite的创建过程(提供小粒度的对外接口).

 

Factory Method

意图:定义一个用于创建对象的接口,让子类决定将哪一个类实例化Factory Method使一个类的实例化延迟到其子类。

动机: 让子类自己决定所需创建的对象,这里的对象一般并非子类本身,而是子类所需要的其他对象.

适用性: 常用于调用统一的抽象类接口,而让子类自己决定(实现)所需创建的对象,由此,封装了子类所创建对象的类型.

结构: 一般内嵌于具有继承关系的子类中,很少单独使用.抽象类提供创建接口,子类实现.典型例子: STL中迭代器的创建(end(),begin()).

优点: 封装了子类所创建对象的类型.

组合模式: 经常用于Template Method中的子类中,使创建过程成为Template Method的一个步骤.

 

Prototype

意图:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。

动机: 拷贝已有对象(包括状态).

适用性: 常用于包含状态的对象拷贝,简化状态的同步过程.典型例子:CAD.

结构: 对象既有自拷贝的对外接口.注意区分浅拷贝和深拷贝,多线程也需要注意.

优点: 简化了对象状态的重新构建.

组合模式: 有时与工厂Factory一同使用,通过单一的接口来创建所需的对象.(Map+clone)

 

Singleton

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

动机: 保证一个类仅有一个实例.

适用性: 一般,只有在当对象因存在多个副本引发性能问题时,才考虑使用Singleton来限制对象实例化.

结构: 常常和静态变量等相关.

优点: 限制对象实例化,解决系统的性能问题.

缺点: Singleton本质上是一个全局变量,全局变量并不是什么好东西,不好管理,且容易引发其他问题.一般并不推荐使用Singleton.

 

Object Pool

意图: 在创建对象比较昂贵,或者对于特定类型能够创建对象数目有限制时,管理对象的重用.

动机: 封装对重用对象的管理.

适用性: 当对象的创建和/或管理必须遵循一组定义明确的规则集, 并且这些规则都与如何创建对象,创建多少对象和在已有对象完成当前任务时如何重用它们等相关时,使用Object Pool来创建和管理对象.

结构: 类似于简单的Factory,即使用单独的类。

优点: 封装对重用对象的管理.

 

Creation Method

意图: 基于类的不同构造函数,提供多种不同的对象构建过程

动机: 使用不同的Cteation函数来区别所创建对象的用途(意图).

适用性: 类中存在多种不同的构造函数,并且每种构造函数所构建的类有不同的用途/意图.

结构: 一般Creation函数需要是static函数,根据所使用的语言而定.类的构造函数为私有.

优点: 根据不同的Creation函数名,可以很清楚的知道构建对象的用途.

变化: 有时直接把派生类的构建直接放入抽象类中,并使用Creation Method.

 

 

结构型模式

结构型模式涉及到如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来

组合接口或实现。结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法。因为可以在运行时刻改变对象组合关系,所以对象组合方式具有更大的灵活性,而这种机制用静态类组合是不可能实现的。

 

Adapter

意图:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

动机: 重用已有的类.

适用性: 重用已有的类去完成正在编写的类中所需的功能.一般原有的类的功能应该和所有类的功能相近,或者更多.

结构: 使用对象组合.不一定就用于子类的编写,也可以是普通类的编写,因此继承关系并不是必须的.典型例子:ACE对系统调用函数的封装.

优点: 重用代码,简化类的编写和测试.

 

Bridge

意图:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

动机: 分离对外接口和内部实现.本质上就是分离两个变化点,并单独封装其变化.

适用性: 用于具有两个变化点的抽象概念的分离.

结构: 使用两个抽象封装不同的变化点.

优点: 简化类之间的继承关系(理论上,一个抽象一个变化点).隐藏实现的变化对客户的影响.

 

Composite

意图:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得客户对单个对象和复合对象的使用具有一致性。

动机: 隐藏“一对多”的关系,让客户一致的对待不同的对象.

适用性: 常用于结构上具有显式树型结构的对象关系.当使用一致的方式对待列表中的每个对象的情况时,可以使用Composite封装对对象列表的管理和遍历,简化客户代码对对象的管理,使客户能够使用一致的代码来对待所有对象.

结构: 常常在Composite的一个派生类中,使用列表来管理其他的派生对象;或者基于递归组合来组织可变数目的对象

优点: 封装了“一对多”的对象关系,使客户能够统一的对待所有的派生对象.省去客户自行管理对象的行为和代码.

组合模式: 可以和Command对象一起使用,封装“一对多”的关系,当然如果Commad对象需要考虑顺序或者其他情况的时候,应该使用Builder来构建.常常使用Builder来构建Composite.也可以和Iterator来遍历对象.

 

 

Decorator

意图:动态地给一个对象添加一些额外的职责。就扩展功能而言, Decorator模式比生成子类方式更为灵活。

动机: 复用现有的类,在不破坏原来的条件下, 添加一些额外的职责/功能.

适用性: 存在一个具备核心功能的类,但是同时需要一些附加的功能.使用Decorator可以在不破坏原类的条件下,动态的添加所需的附加功能.

结构: 如果需要动态的替换,继承关系是必须的,在继承关系下,只需在复用原类接口下改写部分接口即可.但是如果没有替换要求,没必要一定使用继承关系,只需简单的适配所需的接口即可.

优点: 代码复用;容易在核心功能代码的基础上增加额外的功能.

 

Facade

意图:为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

动机: 封装复杂的子系统,使客户更容易使用.同时也封装了原系统变化所引起的客户代码变化.

适用性: 存在功能复杂或易变的子系统,使用Façade增加一个封装层,方便用户使用.

结构: 使用一个类为子系统提供一组易用的对外接口.

优点: 简化编程,隔离变化.

 

Flyweight

意图:运用共享技术有效地支持大量细粒度的对象。

动机: 对象共享,提供性能

其他: 没用过,不清楚

 

Proxy

意图:为其他对象提供一个代理以控制对这个对象的访问。

动机: 对某个对象的访问,有特殊的要求.比如需要跨越网络障碍等.

其他: 没用过,不清楚

优点: Proxy是一个重型模式,一般不推荐使用,毕竟可以使用Facade代替.

 

Compose Method

意图: 将一个比较复杂(逻辑或步骤多)的方法(接口实现)转化成为一系列意图自明的更小方法(接口).

动机: 提供代码的可读性.

适用性: 所有的复杂的接口实现.该模式经常用于重构现有的代码.

结构: 用更小的接口封装代码行.

优点: 能够大大改善代码的可读性,如果接口名字取的好的话.

组合模式: 可与任何模式相结合,改善代码的质量.

 

Collecting Parameter

意图: 使用对象(结构体)在不用的地方进行参数收集.

动机: 所需的参数分散在系统的不同地方,使用该模式简化参数的收集和传递.

适用性: 用于类之间的数据收集和传递,也适用于类内部的代码收集和传递.

结构: 一般使用结构体来存放所需参数.

优点: 有利于接口之间和对象之间的参数传递.

组合模式: 常和Compose Method方法一起使用.

 

行为型模式

行为模式涉及到算法和对象间职责的分配。行为模式不仅描述对象或类的模式,还描述

它们之间的通信模式。这些模式刻画了在运行时难以跟踪的复杂的控制流。它们将你的注意

力从控制流转移到对象间的联系方式上来。

    行为型模式的三个典型特点:

封装变化

对象作为参数

对发送者和接收者解耦

 

Chain of Responsibility

意图:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。

动机: 解除请求的发送者和接收者之间耦合,在对象间传递待处理的请求.

适用性: 使用于请求的跨层传递,接触请求发送者和请求的最终处理者之间的耦合.统一地处理客户请求.

结构: 客户请求一般采用Command封装,使易于传递.一般,不同对象采用统一的接口来处理请求.如果请求处理者对象存放在列表中,一般要求使用继承关系.

优点: 易于请求的跨层传递;解除对象耦合(封装请求的真实处理者);统一请求的处理(封装请求的变化).

组合模式: 经常采用Command来封装不同类型的请求.

 

Command

意图将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。

动机: 封装请求的类型;让请求具有对象的特性(具有状态),这样就可以传递,保存或者采用不同的方式来处理请求对象.

结构: 用继承来封装请求的类型;除了不一样的构造和初始化函数外,提供一致的核心接口.

优点: 让请求具有对象的特性,使客户能够采用不同的方式来处理请求对象,比如可以解除对象构造和对象使用的耦合,即实体解耦和时间解耦.

用途: 经常用于数据库事务操作,设备控制,多线程核心(Active Object)以及GUIdo/undo管理等.或者用于消除过多的条件分派.

组合模式: 经常用于Chain of Responsibility中的请求封装;经常和Composite组合使用,提供统一的对待Command的途径,封装“一对多”的关系.

 

Interpreter

意图:给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用自身定义表示来解释语言中的句子。

动机(个人理解): 采用不同的对象来表示不同的文法,使文法易于组合使用.提供一种可选择的方式,将易变的组合逻辑推给客户代码.

 

Iterator

意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。

动机: 封装对对象的访问规则或者算法.

 

Mediator

意图:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

动机: 封装多个对象间的交互关系,解除耦合.

适用性: 适用于多个对象交互关系复杂且易变的情况.

结构: 提供一个幕后类来统一管理不同对象间的交互关系.

优点: 封装多个对象间的交互关系,使客户更加容易编程.

 

Memento

意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。

动机: 保存对象状态,用于对象状态的回滚或重新构造.

适用性: 用于对象内部状态易变,且对象状态具有某种价值的场合.

结构: 使用简单的结构体即可完成任务.

优点: 提供保存对象的另一种选择.

 

Observer

意图:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

动机: 封装对象间一对多的依赖关系,提供统一的管理点.

适用性: 所有具有一对多依赖关系且需要传递状态信息的对象管理.

结构: 使用双抽象结构,一个抽象管理状态的通知行为(简化派生类的行为),一个抽象用于封装不同的状态观察者.

优点: 在具有多个观察者时,可以简化状态通知部分的Hard-coding,并且易于扩展.

变化: 在状态比较复杂的情况下,一般采用某种约定的参数来提示观察者状态发生了什么样的变化,简化对象更新过程.

 

State

意图:允许一个对象在其内部状态改变时改变它的行为(外部行为)。对象看起来似乎修改了它所属的类。

动机: 分离状态机的逻辑和动作;或者是分离状态和行为(动作).

 

补充:有限状态自动机(FSM)

有限状态自动机的两种表示方式: 状态迁移图(STD)和状态迁移表(STT)

状态迁移图(STD)至少由4部分组成.圆形表示状态;连接状态的箭头被称为迁移;迁移被用一个后面跟着动作名的时间做了标记,组成事件/动作对.类似数字电子中的状态转移图.本质上就是一个东西.

状态迁移表(STT)用一个表的形式来描述系统中状态的转移.表中的每一列表示一个状态迁移的完整过程.由下面4个部分组成一列:

起始状态   触发迁移的事件  终止状态   所执行的动作

 

使用状态迁移图(STD)和状态迁移表(STT)来描述自动状态机是非常有效的,并且非常容易检测那么未知的以及没有处理的状态转移情况.这对编程是很有帮助的,因为在实际编码中,非常容易遗漏非正常的状态转移,而这些遗漏往往是错误的根源.

实现有限状态自动机(FSM)的技术:

嵌套的switch/case语句,解释迁移表和State模式.

在简单其状态迁移中,使用嵌套的switch/case语句就足够的.在复杂情况下,使用State模式比较好. 解释迁移表也容易实现,但是不是这里讨论的目标,最主要的就是表的查找.

State的标准结构图:

 

State模式彻底的分离了状态机的逻辑和动作.动作是在Context类中实现的,而逻辑则是分布在State类的派生类中.这使得二者可以非常容易的独立变化,互补影响.例如,只要使用State的另一个派生类.就可以非常容易地在一个不同的状态逻辑中重用Context类的动作.此外,我们也可以在不影响State派生类逻辑的情况下创建Context的派生类来更改或者替换动作的实现.

可以使用状态机的地方:

作为GUI中高层应用策略;GUI交互控制器;分布式处理等.几乎凡有状态存在的地方均可以考虑采用有限状态机.

 

 

Strategy

意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。

动机: 封装不同的算法.定制一组可以互换的算法族.

适用性: 同一个问题存在多种不同的解决方案.

结构: 为了满足算法之间的互换性,必须使用继承,并且遵循Liskov原则.

优点: 封装算法的变化.

组合模式: 经常在使用算法的基类Context中使用Template Method.如果Context的派生类中要求所使用的算法动态改变,还常常把Factory Method内嵌到派生类中来创建不同的算法类.

 

 

Template Method

意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

动机: 消除子类中的重复代码,简化子类代码.

适用性: 当各个子类中,混杂着不变和可变的行为时,就可以使用该模式.将不变的行为放入父类中,子类只需定制可变的行为.这里不变还包括行为的执行顺序.

结构: 必须使用继承关系.

优点: 消除子类的重复行为.

组合模式: 经常和Strategy一起使用.在分解不变和可变行为时,还常可以借助组合方法(Compose Method)Collecting Parameter模式.

 

 

Visitor

意图:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

动机: 在不改变原类(一般是特定的数据结构)的情况下,增加新的功能接口.

适用性: 原有的类结构不能或者不容易改变;或者新加的功能不属于原类的职责范畴.

结构: 双重分派.增加一个Visitor,并针对所有要访问的派生类增加单独的访问接口(在Visitor).本质上,Visitor模式中的两次分派形成一个功能矩阵.Visitor的接口名字和其接受的派生类类型分别是功能矩阵的两个变化轴.

优点: 使用Visitor模式,使程序中的数据结构(原类)独立于它的用途.

用途: 一般如果应用程序中存在有需要以多种不同方式进行解释的数据结构,就可以使用Visitor模式.比如使用Visitor模式来遍历所有的配置数据来初始化不同的应用程序子系统.最常见的应用:遍历大量的数据结构并产生不同类型的报表.

 

Null Object

意图: 提供一个没有任何行为的对象.

动机: 消除代码中四处存在的无效对象判断

适用性: 只要对无效对象的判断逻辑多次出现时,就有引入Null Object的必要.

结构: Null Object肯定是作为派生类的一个种类出现,并用于取代没有合适派生类可用的情形.比如,对象放在Map,查找可能无效.

优点: 消除对无效对象的判断逻辑,提供系统的可靠性.

 

备注:

1 模式常常相互配合,共同解决问题.

2 模式是特定场景下优雅的解决方案,因此场景很关键.在软件设计中,特定的场景.可能是显而易见的,可能是隐而不现的,有时甚至是设计者有意创造的.因此使用模式时,对问题的分析很重要.

3 模式的使用是有先后之分的.

4 DP书中所给的结构图仅仅是模式可能的实现方式之一,但不是唯一.实现一个模式往往有多种途径.

5 模式本身是比较复杂的.纯粹的为了模式而模式,并不会改善设计,相反会引入不必要的复杂性.之所以使用模式,是因为它提供了一种优雅的解决方案.

6 有些模式,比如行为型的模式等,常常在设计初期就引入,但是更多的模式则不宜过早的引入. “Refactoring to Patterns”是目前普遍公认的最好的使用设计模式的方法。

7 模式和语言无关.

8 使用模式不难,用好模式难.唯一的建议: 多编程.

 

 

重构与模式

很多模式并非作为主模式而被引入系统的设计中,往往是在系统不断进化的过程中,根据需求而引入.模式的引入过程就是系统的一个组合重构过程.进化的系统需要持续重构,因此重构就成了使用模式的一个很好的途径.

重构的目标:消除重复代码;消除冗余代码;简化代码逻辑.本质上,重构就是为了提高代码的可读性和可维护性.

重构往往需要测试驱动开发的支持. 没有测试代码重构很难进行,测试代码是保证重构不破坏代码原有行为的必要条件.

重构过程不宜采取过大的步骤.采取尽可能小的重构步骤,在不破坏代码行为的前提下, 尽量使重构的代码时刻保持可编译状态下进行代码的增删和替换.小步骤的重构往往就是最快的重构方式.

模式导向的重构是把重构和模式使用结合在一起的一个途径。

 

 

参考书籍

设计模式       1995

设计模式精解   2006

敏捷软件开发   2003

Refactoring to Patterns(中文: 重构与模式)  2006

 

 

推荐书籍:

测试驱动开发(TDD)     Kent Beck

重构                  Martin Fowler

Head First Design Patterns               Elisabeth Freeman, Eric Freeman

Patterns of Enterprise Application Architecture   Martin Fowler

Extreme Programming Explained             Kent Beck

Object-oriented Software Construction         Bertrand Meyer

UML Distilled(有中文版)                   Martin Fowler

 

 

大部分写于:2007-02-05~06

完成于2007-03-30

 
原创粉丝点击