1、什么是设计模式?
每一个模式描述了一个在我们周围不断重复发生的事情,以及该问题的解决方案的核心。
一般而言,一个模式有四个基本要素:
- 模式名称 — 一个助记名,它用一两次来描述模式的问题、解决方案和效果。
- 问题 — 描述了应该在何时使用模式。解释了设计问题和问题存在的前因后果。
- 解决方案 — 描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。
- 效果 — 描述了模式应用的效果及使用模式应权衡的问题。(包括它对系统的灵活性、扩充性或可移植性)
2、描述设计模式
同时记录设计产生的决定过程、选择过程和权衡过程。
模式名和分类
模式名简洁的描述了模式的本质。
意图
是回答下列问题的简单描述:设计模式是做什么的?它的基本原理和意图是什么?它解决的是什么样的特定设计问题?
别名
模式的其他名称。
动机
用以说明一个设计问题以及如何用模式中的类、对象来解决该问题的特定情景。该情景会帮助你理解随后对模式更抽象的解释。
适用性
什么情况下可以使用该设计模式?该模式可用来改进哪些不良设计?你怎样识别这些情况?
结果
采用基于对象建模技术的表示法对模式中的类进行图形描述。
参与者
指设计模式中的类和/或对象以及它们各自的职责。
协作
模式的参与者怎样协作以实现它们的职责
效果
模式怎样支持它的目标?使用模式的效果和所需做的权衡取舍?系统结构的哪些方面可以独立改变?
实现
实现模式需要知道的一些提示、技术要点及应避免的缺陷,以及是否存在某些特定于实现语言的问题。
相关模式
与这个模式紧密相关的模式有哪些?其间重要的不同之处是什么?这个模式应与哪些其他模式一起使用?
3、设计模式怎样解决设计问题?
-
寻找合适的对象
面向对象程序由对象组成,对象包括数据和数据进行操作的过程,过程通常称为方法或操作。 对象在收到客户的请求(或消息)后执行相应的操作。 客户请求是使对象操作执行的唯一方法,操作又是对象改变内部数据的唯一方法。 由于这些限制,对象的内部状态是被封装的,它不能被直接访问,它的表示对于对象外部是不可见的。 面向对象设计最困难的部分是将系统分解成对象集合。 因为要考虑许多因素:封装、粒度、依赖关系、灵活性、性呢过、演化、复用等等。
当给对象发送请求时,所引起的具体操作既与请求本身有关又与接收对象有关。 支持相同请求的不同对象可能队请求激发的操作有不同的实现。 发送给对象的请求和它的相应操作在运行时刻的链接就称之为**动态绑定**。 动态绑定是指发送的请求直到运行时刻才受你的具体的实现的约束。 因而,在之道任何有正确接口的对象都将接受此请求时,你可以写一个一般的程序, 它期待着哪些具有该特定接口的对象。进一步讲,动态绑定允许你在运行时刻彼此替换有相同接口的对象。 这种可替换性就称为**多态**。多态允许客户对象仅要求其他对象支持特定接口,除此之外对其架设几近于无。
描述对象的实现
抽象类的主要目的是为它的自雷定义公共接口。一个抽象类将把它的部分或全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。
在抽象类中定义却没有实现的操作被称为抽象操作。
非抽象类称为具体类。
-
对接口编程,而不是对实现编程
-
优先使用对象组合,而不是类继承
委托的主要优点在于它便于运行时刻组合对象操作以及改变这些操作的组合方式。委托与那些通过对象组合以取得软件灵活性的技术一样,具有如下不足之处:动态的、高度参数化的软件比静态软件更难于理解。还有运行低效问题。只有当委托使设计比较简单而不是更复杂时,它才是好的选择。
委托是对象组合的特例。它告诉你对象组合作为一个代码复用机制可以替代继承。
聚合意味着一个对象拥有另一个对象或对另一个对象负责。一般我们称一个对象包含另一个对象或者是另一个对象的一部分。聚合意味着聚合对象和其所有者具有相同的生命周期。
相识意味着一个对象仅仅知道另一个对象。有时相识也被称为”关联“或”引用“关系。相识的对象可能请求彼此的操作,但是它们不为对方负责。相识是一种比聚合要弱的关系,它只标识了对象间较松散的耦合关系。
4、设计应支持变化
获得最大限度复用的关键在于对新需求和已知需求发生变化时的预见性,要求你的系统设计要能够相应的改进。
为了设计适应这种变化,且据以偶健壮性的系统,你必须考虑系统在它的生命周期内会发生怎样的变化。一个不考虑系统变化的设计在将来就有可能需要重新设计。这些变化可能是类的重新定义和实现,修改客户和重新测试。重新设计会影响软件系统的许多方面,并且未曾料到的变化总是代价巨大的。
设计模式可以确保系统能以特定方式变化,从而帮助你避免重新设计系统。每一个设计模式允许系统结构的某个方面的变化独立于其他方面,这样产生的系统对于某一种特殊变化将更健壮。
下面阐述一些导致重新设计的一般原因:
-
通过显式的制定一个类来创建对象
在创建对象时制定类名将使你受特定实现的约束而不是特定接口的约束。 这会使未来的变化更复杂。要避免这种情况,应该间接的创建对象。
-
对特殊操作的依赖
当你为请求指定一个特殊的操作时,完成该请求的方式就固定下来了。 为避免把请求代码写死,你将可以在编译时刻或运行时刻很方便的改变相应请求的方法。
-
对硬件和软件平台的依赖
外部的操作系统接口和应用编程接口(API)在不同的软硬件平台上是不同的。 依赖于特定平台的软件将很难移植到其他平台上,甚至都很难跟上本地平台的更新。 所以设计系统时限制其平台相关性就很重要了。
-
对对象表示或实现的依赖
知道对象怎样表示、保存、定位或实现的应用端在对象变化时可能也需要变化。对应用端隐藏这些信息能阻止连锁变化。
-
算法依赖
算法在开发和复用时常常被扩展、优化和替代。 依赖于某个特定算法的对象在算法发生变化时不得不变化。 因此有可能发生变化的算法应该被孤立起来。
-
紧耦合
紧耦合的类很难独立的被复用,因为它们是相互依赖的。 紧耦合产生单块的系统,要改变或删除一个类,你必须理解和改变其他许多类。 这样的系统是一个很难学习、移植和维护的密集体。
-
通过生成子类来扩充功能
通常很难通过定义子类来定义对象。每一个新类都有固定的实现开销(初始化、终止操作等)。 定义子类还需要对父类有深入的了解。如:重新定义一个操作可能需要重定义其他操作。一个被重新定义的操作可能需要调用继承下来的操作。并且子类方法会导致类爆炸,因为即使对于一个简单的扩充,你也不得不引入许多新的子类。 一般的对象组合技术和具体的委托技术,是继承之外组合对象行为的另一种灵活方法。 新的功能可以通过以新的方式组合已有对象,而不是通过定义已存在类的子类的方式加到应用中去。另一方面, 过多使用对象组合会使设计难于理解。许多设计模式产生的设计中,你可以定义一个子类,且将它的实例和已存在实例进行组合来引入定制的功能。
-
不能方便的对类进行修改
有时你不得不改变一个难以修改的类。
5、怎样选择设计模式
1、考虑设计模式是怎样解决设计问题的
2、浏览模式的意图部分
3、研究模式怎样互相关联
4、研究目的相似的模式
5、检查重新设计的原因
6、考虑你的设计中哪些是可变的
目的 | 设计模式 | 可变的方面 |
---|---|---|
创建 | Abstract Factory Builder Factory Method Prototype Singleton | 产品对象家族 如何创建一个组合对象 被实例化的子类 被实例化的类 一个类的唯一实例 |
结构 | Adapter Bridge Composite Decorator Facade Flyweight Proxy | 对象的接口 对象的实现 一个对象的结构和组成 对象的职责,不生成子类 一个子系统的接口 对象的存储开销 如何访问一个对象,该对象的位置 |
行为 | Chain of Responsibility Command Interpreter Iterator Mediator Memento Observer State Strategy Template Method Visitor | 满足一个请求的对象 何时、怎样满足一个请求 一个语言的文法及解释 如何遍历、访问一个聚合的各元素 对象间怎样交互、和谁交互 一个对象中哪些私有信息存放在该对象之外,以及在什么时候进行存储 多个对象依赖于另外一个对象,而这些对象又如何保持一致 对象的状态 算法 算法中的某些步骤 某些可作用于一个(组)对象上的操作,但不修改这些对象的类 |
6、怎样使用设计模式
一旦你选择了一个设计模式,你怎么使用它呢?这里给出一个有效应用设计模式的循序渐进的方法。
-
大致浏览一遍模式
特别注意其适用性部分和效果部分,确定它适合你的问题。
-
回头研究结构部分、参与者部分和协作部分
确保你理解这个模式的类和对象以及它们是怎样关联的。
-
看代码示例部分,看看这个模式代码形式的具体例子
研究代码将有助于你实现模式。
-
选择模式参与者的名字,使它们在应用上下文中有意义
设计模式参与者的名字通常过于抽象而不会直接出现在应用中。 然而,将参与者的名字和应用中出现的名字合并起来是很有用的,这会帮助你在实现中更显式的体现出模式来。
-
定义类
声明它们的接口,建立它们的继承关系,定义代表数据和对象引用的实例变量。 识别模式会影响到的你的应用中存在的类,做出相应的修改。
-
定义模式中专用于应用的操作名称
使用与每一个操作相关联的责任和协作作为指导。
-
实现执行模式中责任和协作的操作
实现部分提供线索指导你进行实现。