第一章 引言

为什么面向对象设计困难?

设计面向对象软件比较困难,而设计可复用的面向对象软件就更加困难。

  • 你必须找到相 关的对象,
  • 以适当的粒度将它们归类,
  • 再定义类的接口和继承层次,
  • 建立对象之间的基本关 系。
  • 你的设计应该对手头的问题有针对性,同时对将来的问题和需求也要有足够的通用性。
  • 你也希望避免重复设计或尽可能少做重复设计

模式的定义?

模式每一个模式描述了一个在我们周围不断重复发生的问题, 以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。 一个模式有四个基本要素

  • 模式名称(design name): 一个助记名,用一两个词描述问题、解决方案和效果
  • 问题(problem): 描述了何时使用模式
    • 可能描述了特定的设计问题,如怎样用对象表示算法
    • 可能描述了导致不灵活设计的类和对象结构
    • 部分包括了使用模式必须满足的一系列先决条件
  • 解决方案(solution): 描述了设计的组成部分
    • 方案需要经历的过程见章节第一部分
    • 就像是一个模版/骨架,具体问题填充不同的内容。不描述一个特定而具体的设计或实现,而是提供对问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题
  • 效果(consequences): 模式应用的效果及使用模式应权衡的问题
    • 软件效果大多关注对时间和空间的衡量,它们也表述了语言和实现问题
    • 因为复用是面向对象设计的要素之一,所以模式效果包括它对系统的灵活性、扩充性或可移植 性的影响
    • 显式地列出这些效果对理解和评价这些模式很有帮助
    • 对于评价设计选择和理解使用模式的代价及好处具有重要意义

如何描述设计模式

为了达到设计复用,我们必须同时记录设 计产生的决定过程、选择过程和权衡过程。具体的例子也是很重要的,它们让你看到实际的 设计。 同意的描述格式

  • 模式名: 概括性词语
  • 别名: 别名
  • 意图: 解决哪一类问题
  • 动机: 一类问题中具体一种场景
  • 适用性: 何时使用,如何识别具体场景,如何改进设计
  • 结构: 对象建模技术的图形描述
  • 参与者: 类和对象,以及职责
  • 协作: 参与者怎样协作实现各自职责
  • 效果: 权衡带来的效果和实施成本;是否支持目标;对系统结构灵活性增强在哪些方面
  • 实现:
  • 代码示例:代码描述模式
  • 已知应用:哪些框架活着应用
  • 相关模式:相似的模式有哪些,其间重要的不同之处?应与哪些模式一起配合使用

设计模式分类

原则

  • 目的。模式是用来完成什么工作的。创建型、结构型、行为型
  • 范围。模式主要是用于类还是对象。用于类的在编译时刻便确定下来了,用于对象的运行时刻是可以变化的,更具动态性。

具体分类见列表

  • 创建型 结构型 行为型  
    Factory Method Adapter Interpreter、Template Method
    对象 AbstractFactory、Builder、Prototype、Singleton Adapter(对象)、Bridge、Composite、Decorator、Facade、Flyweight、Proxy ChainofResponsibility、Command、Iterator、Mediator、Memento、Observer、State、Strategy、Visitor

如何选择设计模式

  1. 考虑是怎样解决问题的
  2. 浏览模式的意图
  3. 研究模式怎么相互关联。可以获得合适的模式或模式组
  4. 研究目的相似的模式
  5. 检查重新设计的原因。研究该模式不能应对的情况,需要重新设计
  6. 考虑设计中哪些是可变的。check该模式可以对哪些方面变化拓展;

如何使用/认识设计模式

  1. 概览模式,注意适用性和效果,确定适合问题
  2. 研究结构、参与者和协作。确保理解模式的类和对象以及关联
  3. 选择模式参与者名字,使其在上下文中有意义
  4. 定义类、接口,建立关系。识别模式对现有应用造成的影响
  5. 定义模式中专用于应用的操作名称,名字一般依赖与应用。

理解Class和Interface的区别

一个对象的类定义了对象是怎样实现的,同时也定义了对象的内部状态和操作的实现。但是对象的类型只与它的接口有关,接口即对象能响应的请求的集合。一个对象可以有多个类型,不同类的对象可以有相同的类型。

理解类继承和接口继承 (或子类型化 )之间的差别也十分重要。类继承根据一个对象的实现 定义了另一个对象的实现。简而言之,它是代码和表示的共享机制。然而,接口继承 (或子类 型化)描述了一个对象什么时候能被用来替代另一个对象。

对接口编程,而不是对实现编程。创建型模式确保你的系统是采用针对接口的方式书写的,而不是针对实现而书写 的。增强灵活性,客户端不需知道具体实现类型。 优先使用对象组合,而不是类继承。杜绝类爆炸,增强复用性。

导致重新设计的一般原因

  1. 通过显式地指定一个类来创建对象在创建对象时指定类名将使你受特定实现的约束而不是特定接口的约束。这会使未来的变化更复杂。要避免这种情况,应该间接地创建对象。 设计模式:AbstractFactory(3.1),FactoryMethod(3.3),Prototype(3.4)。
  2. 对特殊操作的依赖当你为请求指定一个特殊的操作时,完成该请求的方式就固定下来了。为避免把请求代码写死,你将可以在编译时刻或运行时刻很方便地改变响应请求的方法。 设计模式:ChainofResposibility(5.1),Command(5.2)。
  3. 对硬件和软件平台的依赖外部的操作系统接口和应用编程接口(API)在不同的软硬件 平台上是不同的。依赖于特定平台的软件将很难移植到其他平台上,甚至都很难跟上本地平台的更新。所以设计系统时限制其平台相关性就很重要了。 设计模式:AbstractFactory(3.1),Bridge(4.2)。
  4. 对对象表示或实现的依赖知道对象怎样表示、保存、定位或实现的客户在对象发生变化时可能也需要变化。对客户隐藏这些信息能阻止连锁变化。 设计模式:AbstractFactory(3.1),Bridge(4.2),Memento(5.6),Proxy(4.7)
  5. 算法依赖算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此有可能发生变化的算法应该被孤立起来。 设计模式:Builder(3.2),Iterator(5.4),Strategy(5.9),TemplateMethod(5.10),Visitor(5.11)
  6. 紧耦合紧耦合的类很难独立地被复用,因为它们是互相依赖的。紧耦合产生单块的系统,要改变或删掉一个类,你必须理解和改变其他许多类。这样的系统是一个很难学习、移植和维护的密集体。 松散耦合提高了一个类本身被复用的可能性,并且系统更易于学习、移植、修改和扩展。设计模式使用抽象耦合和分层技术来提高系统的松散耦合性。 设计模式:AbstractFactory(3.1),Command(5.2),Facade(4.5),Mediator(5.5),Observer(5.7),ChainofResponsibility(5.1)。
  7. 通过生成子类来扩充功能通常很难通过定义子类来定制对象。每一个新类都有固定的实现开销(初始化、终止处理等)。定义子类还需要对父类有深入的了解。如,重定义一个操作可能需要重定义其他操作。一个被重定义的操作可能需要调用继承下来的操作。并且子类方法会导致类爆炸,因为即使对于一个简单的扩充,你也不得不引入许多新的子类。 一般的对象组合技术和具体的委托技术,是继承之外组合对象行为的另一种灵活方法。新的功能可以通过以新的方式组合已有对象,而不是通过定义已存在类的子类的方式加到应用中去。另一方面,过多使用对象组合会使设计难于理解。许多设计模式产生的设计中,你可以定义一个子类,且将它的实例和已存在实例进行组合来引入定制的功能。 设计模式:Bridge(4.2),ChainofResponsibility(5.1),Composite(4.3),Decorator(4.4),Observer(5.7),Strategy(5.9)。
  8. 不能方便地对类进行修改有时你不得不改变一个难以修改的类。也许你需要源代码而又没有(对于商业类库就有这种情况),或者可能对类的任何改变会要求修改许多已存在的其他子类。设计模式提供在这些情况下对类进行修改的方法。 设计模式:Adapter(4.1),Decorator(4.4),Visitor(5.11)。

设计模式在开发如下三类主要软件中所起的作 用:应用程序、工具箱和框架。

应用程序强调具体接入场景

工具箱强调的是代码复用,它们 是面向对象环境下的“子程序库”。

框架(Framework)是构成一类特定软件可复用设计的一组相互协作的类。 框架规定了你的应用的体系结构。它定义了整体结构,类和对象的分割,各部分的主要 责任,类和对象怎么协作,以及控制流程。框架预定义了这些设计参数,以便于应用设计者 或实现者能集中精力于应用本身的特定细节。框架记录了其应用领域的共同的设计决策。因 而框架更强调设计复用 ,尽管框架常包括具体的立即可用的子类。

这个层次的复用导致了应用和它所基于的软件之间的反向控制 (inversion of control)。当 你使用工具箱 (或传统的子程序库 )时,你需要写应用软件的主体并且调用你想复用的代码。而 当你使用框架时,你应该复用应用的主体,写主体调用的代码。你不得不以特定的名字和调 用约定来写操作地实现,但这会减少你需要做出的设计决策。

因为模式和框架有些类似,人们常常对它们有怎样的区别和它们是否有区别感到疑惑。 它们最主要的不同在于如下三个方面:

1) 设计模式比框架更抽象 框架能够用代码表示,而设计模式只有其实例才能表示为代 码。框架的威力在于它们能够使用程序设计语言写出来,它们不仅能被学习,也能被直接执 行和复用。而本书中的设计模式在每一次被复用时,都需要被实现。设计模式还解释了它的 意图、权衡和设计效果。 2) 设计模式是比框架更小的体系结构元素 一个典型的框架包括了多个设计模式,而反 之决非如此。 3) 框架比设计模式更加特例化 框架总是针对一个特定的应用领域。一个图形编辑器框 架可能被用于一个工厂模拟,但它不会被错认为是一个模拟框架。而本书收录的设计模式几 乎能被用于任何应用。当然比我们的模式更特殊的设计模式也是可能的 (如,分布式系统和并 发程序的设计模式 ),尽管这些模式不会像框架那样描述应用的体系结构。 框架变得越来越普遍和重要。它们是面向对象系统获得最大复用的方式。较大的面向对 象应用将会由多层彼此合作的框架组成。应用的大部分设计和代码将来自于它所使用的框架 或受其影响。

Comments