设计模式(一)

187 阅读24分钟

1 设计模式的要素

  • “每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”,设计模式的核心在于提供了相关的解决方案,使得人们可以更加简单方便地复用成功的设计利体系结构。
  • 设计模式一般有以下4个基本要素。
    • (1)模式名称(patern name)。一个助记名,它用一两个词来播述模式的问题,解决方案效果。命名一个新的模式增加了设计词汇。设计模式允许在较高的抽象层次上进行设计。于一个模式词汇表,就可以讨论模式并在编写文档时使用它们。模式名可以帮助人们思考,基于人们与其他人交流设计思想及设计结果。找到恰当的模式名也是设计模式工作的难点之一。
    • (2)问题(problem)。问题描述了应该在何时使用模式。它解释了设计问题和问题存在的前因后果,可能描述了特定的设计问题,如怎样用对象表示算法等;也可能描述了导致不灵活计的类或对象结构。有时候,问题部分会包括使用模式必须满足的一系列先决条件。
    • (3)解决方案(solution)。解决方案描述了设计的组成成分、它们之间的相互关系及各自的职责和协作方式。因为模式就像一个模板,可应用于多种不同场合。所以解决方案并不描述一个特定的具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。
    • (4)效果(consequences)。效果描述了模式应用的效果及使用模式应权衡的问题。尽管描述设计决策时,并不总提到模式效果,但它们对于评价设计选择和理解使用模式的代价及好处具有重要意义。软件效果大多关注对时间和空间的衡量,它们也表述了语言和实现问题。因为复用是面向对象设计的要素之一,所以模式效果包括它对系统的灵活性、扩充性或可移植性的影响,显式地列出这些效果对理解和评价这些模式很有帮助。
  • 设计模式确定了所包含的类和实例,它们的角色、协作方式以及职责分配。每一个设计模式都集中于一个特定的面向对象设计问题或设计要点,描述了什么时候使用它,在另一些设计的束条件下是否还能使用,以及使用的效果和如何取舍。按照设计模式的目的可以分为三大类,如表所示。
  • 创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分配职责进行描述。

2 创建型设计模式

  • 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。
  • 随着系统演化得越来越依赖于对象复合而不是类继承,创建型模式变得更为重要。当这种情况发生时,重心从对一组固定行为的硬编码(hard-coding)转移为定义一个较小的基本行为集,这些行为可以被组合成任意数目的更复杂的行为。这样创建有特定行为的对象要求的不仅仅是实例化一个类。
  • 在这些模式中有两个不断出现的主旋律。第一,它们都将关于该系统使用哪些具体的类的信息封装起来。第二,它们隐藏了这些类的实例是如何被创建和放在一起的。整个系统关于这些对象所知道的是由抽象类所定义的接口。因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以及何时创建这些方面给予了很大的灵活性。它们允许用结构和功能差别很大的“产品”对象配置一个系统。配置可以是静态的(即在编译时指定),也可以是动态的(在运行时)。

2.1 Abstract Factory(抽象工厂)

  • 1)意图
    • 提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
  • 2)结构
    • 抽象工厂模式的结构如图所示。
    • 其中:
      • AbstractFactory 声明一个创建抽象产品对象的操作接口。
      • ConcreteFactory实现创建具体产品对象的操作。
      • AbstractProduct 为一类产品对象声明一个接口。
      • ConcreteProduct定义一个将被相应的具体工厂创建的产品对象,实现AbstractProduct接口。
      • Client 仅使用由AbstractFactory 和AbstractProduct 类声明的接口。
  • 3)适用性
    • Abstract Factory 模式适用于:
      • 一个系统要独立于它的产品的创建、组合和表示时。
      • 一个系统要由多个产品系列中的一个来配置时。
      • 当要强调一系列相关的产品对象的设计以便进行联合使用时。
      • 当提供一个产品类库,只想显示它们的接口而不是实现时。

2.2 Builder(生成器)

  • 1)意图
    • 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  • 2)结构
    • 生成器模式的结构如图所示。
    • 其中:
      • Builder为创建一个Product对象的各个部件指定抽象接口。
      • ConcreteBuilder实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,提供一个检索产品的接口。
      • Director构造一个使用Builder接口的对象。
      • Product表示被构造的复杂对象。ConcreteBuilder 创建该产品的内部表示并定义它的装配过程。包含定义组成组件的类,包括将这些组件装配成最终产品的接口。
  • 3)适用性
    • Builder 模式适用于:
      • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
      • 当构造过程必须允许被构造的对象有不同的表示时。

2.3 Factory Method(工厂方法)

  • 1)意图
    • 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
  • 2)结构
    • 工厂方法模式的结构如图所示。
    • 其中:
    • Builder 模式适用于:
      • Product定义工厂方法所创建的对象的接口。
      • ConcreteProduct 实现Product接口。
      • Creator 声明工厂方法,该方法返回一个Prodiuet类型的对象。Creator 也可以定义一个工厂方法的默认实现,它返回一个默认的ConcreteProduet 对象,可以调用工厂方法以创建一个Product对象。
      • ConcreteCreator 重定义工厂方法以返回一个ConcreteProduct 实例。
  • 3)适用性:
    • Factory Method模式适用于:
      • 当一个类不知道它所必须创建的对象的类的时候。
      • 当一个类希望由它的子类来指定它所创建的对象的时候。
      • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

2.4 Prototype(原型)

  • 1)意图
    • 用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
  • 2)结构
    • 原型模式的结构如图所示。
    • 其中:
      • Prototype声明一个复制自身的接口。
      • ConcretePrototype实现一个复制自身的操作。
      • Client 让一个原型复制自身从而创建一个新的对象。
  • 3)适用性
    • Prototype模式适用于:
      • 当一个系统应该独立于它的产品创建、构成和表示时。
      • 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
      • 为了避免创建一个与产品类层次平行的工厂类层次时。
      • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

2.5 Singleton(单例)

  • 1)意图
    • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 2)结构
    • 单例模式的结构如图所示。
    • 其中:Singleton指 定一个Iinstance 操 作,允许客户访问它的唯 一实例。Ihnstance 是一个类操作,可能负责创建它自己的唯一实例。
  • 3)适用性
    • Singleton模式适用于:
      • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
      • 当这个唯一实例应该是通过子类化可扩展的,并且客户无须更改代码就能使用一个扩展的实例时。

2.6 创建型模式比较

  • 用于系统创建的那些对象的类对系统进行参数化有两种常用方法:生成创建对象的类的子和对系统进行参数化的方法。前者对应于使用Factory Method模式,其主要缺点是仅为了改变产品类就可能需要创建一个新的子类。这种改变可能级联发生,例如,如果产品的创建者本身是一个工厂方法创建的,那么也必须重定义它的创建者。后者更多地依赖于对象的复合,定义一个对象负责明确产品对象的类,并将它作为该系统的参数。这是Abstract Factory、Builder和Pototype模式的关键特征,都涉及创建一个新的负责创建产品对象的“工厂对象”Abstract Factory由这个工厂对象产生多个对象。Builder 由这个工厂对象使用一个相对复杂的协议,逐步创建一个复杂产品。Prototype由该工厂对象通过复制原型对象来创建产品对象。在这种情况下,由于原型负责返回产品对象,所以工厂对象和原型是同一个对象。

3 结构型设计模式

  • 结构型设计模式涉及如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果这个类包含了所有父类的性质。这一模式尤其有助于多个独立开发的类库协同工作。其中一个例子是类形式的Adapter模式。一般来说,适配器使得一个接口与其他接口兼容,从而给出了多个不同接口的统一抽象。为此,类适配器对一个adaptee类进行私有继承。这样,适配器就可以用adaptee的接口表示它的接口。
  • 结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法。因为可以在运行时刻改变对象组合关系,所以对象组合方式具有更大的灵活性,而这种机制用静态类组合是不可能实现的。
  • Composite模式是结构型对象模式的一个实例。它描述了如何构造一个类层次式结构,这一结构由两种类型的对象所对应的类构成。其中的组合对象使得用户可以组合基元对象以及其他的组合对象,从而形成任意复杂的结构。在Proxy模式中,proxy对象作为其他对象的一个方便的替代或占位符。它的使用可以有多种形式,例如可以在局部空间中代表一个远程地址空间中的对象,也可以表示一个要求被加载的较大的对象,还可以用来保护对敏感对象的访问。Proxy模式还提供了对对象的一些特有性质的一定程度上的间接访问,从而可以限制、增强或修改这些性质。Flyweight模式为了共享对象定义了一个结构。至少有两个原因要求对象共享:效率和一致性。Flyweight的对象共享机制主要强调对象的空间效率。使用很多对象的应用必须考虑每一个对象的开销。使用对象共享而不是进行对象复制,可以节省大量的空间资源。但是仅当这些对象没有定义与上下文相关的状态时,它们才可以被共享。Flyweight的对象没有这样的状态。任何执行任务时需要的其他一些信息仅当需要时才传递过去。由于不存在与上下文相关的状态,因此Flyweight对象可以被自由地共享。
  • 如果说Flyweight模式说明了如何生成很多较小的对象,那么Facade 模式则描述了如何用单个对象表示整个子系统。模式中的facade用来表示一组对象,facade的职责是将消息转发给它所表示的对象。Bridge模式将对象的抽象和其实现分离,从而可以独立地改变它们。
  • Decorator模式描述了如何动态地为对象添加职责。Decorator 模式是一种结构型模式,这一模式采用递归方式组合对象,从而允许添加任意多的对象职责。例如,一个包含用户界面组件的 Decorator对象可以将边框或阴影这样的装饰添加到该组件中,或者它可以将窗口滚动和缩放这样的功能添加到组件中。可以将一个Decorator对象嵌套在另外一个对象中,就可以很简单地增加两个装饰,添加其他的装饰也是如此。因此,每个Decorator对象必须与其组件的接口兼容并且保证将消息传递给它。Decorator模式在转发一条信息之前或之后都可以完成它的工作(例如绘制组件的边框)。许多结构型模式在某种程度上具有相关性。

3.1 Adapter(适配器)

  • 1)意图
    • 将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  • 2)结构
    • 类适配器使用多重继承对一个接口与另一个接口进行匹配,其结构如图所示。
    • 对象适配器依赖于对象组合,其结构如图所示。
    • 其中:
      • Target定义Client使用的与特定领域相关的接口。
      • Client与符合Target接口的对象协同。
      • Adaptee定义一个已经存在的接口,这个接口需要适配。
      • Adapter对Adaptee的接口与Target接口进行适配。
  • 3)适用性
    • Adapter模式适用于:
      • 想使用一个已经存在的类,而它的接口不符合要求。
      • 想创建一个可以服用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
      • (仅适用于对象Adapter)想使用一个已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。

3.2 Bridge(桥接)

  • 1)意图
    • 将抽象部分与其实现部分分离,使它们都可以独立地变化。
  • 2)结构
    • 桥接模式的结构如图所示。
    • 其中:
      • Abstraction定义抽象类的接口,维护一个指向Implementor类型对象的指针。
      • RefinedAbstraction 扩充由Abstraction 定义的接口。
      • Implermentor 定义实现类的接口,该接口不一定要与Abstraction 的接口完全一致;事实上这两个接口可以完全不同。一般来说,Implementor接口仅提供基本操作,而Abstraction定义了基于这些基本操作的较高层次的操作。
      • ConcreteImplementor实现Implementor接口并定义它的具体实现。
  • 3)适用性
    • Bridge模式适用于:
      • 不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如,这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
      • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这是Bridge模式使得开发者可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
      • 对一个抽象的实现部分的修改应对客户不产生影响,即客户代码不必重新编译。
      • (C++)想对客户完全隐藏抽象的实现部分。
      • 有许多类要生成的类层次结构。
      • 想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。

3.3 Composite(组合)

  • 1)意图
    • 将对象组合成树型结构以表示“部分一整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
  • 2)结构
    • 组合模式的结构如图所示。
    • 其中:
      • Component为组合中的对象声明接口;在适当情况下实现所有类共有接口的默认行为:声明一个接口用于访问和管理Component的子组件:(可选)在递归结构中定义一个接口,用于访问一个父组件,并在合适的情况下实现它。
      • Leaf在组合中表示叶结点对象,叶结点没有子结点,在组合中定义图元对象的行为。
      • Composite定义有子组件的那些组件的行为;存储子组件;在Component接口中实现与子组件有关的操作。
      • Client 通过Component接口操纵组合组件的对象。
  • 3)适用性
    • Composite模式适用于:
      • 想表示对象的部分一整体层次结构。
      • 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

3.4 Decorator(装饰)

  • 1)意图
    • 动态地给一个对象添加一些额外的职责。就增加功能而言,Decorator模式比生成子类更加灵活。
  • 2)结构
    • 装饰模式的结构如图所示。
    • 其中:
      • Component定义一个对象接口,可以给这些对象动态地添加职责。
      • ConcreteComponent定义一个对象,可以给这个对象添加一些职责。
      • Decorator维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。
      • ConcreteDecorator向组件添加职责。
  • 3)适用性
    • Decorator模式适用于:
      • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
      • 处理那些可以撤销的职责。
      • 当不能采用生成子类的方式进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是,由于类定义被隐藏,或类定义不能用于生成子类。

3.5 Facade(外观)

  • 1)意图
    • 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
  • 2)结构
    • 外观模式的结构如图所示。
    • 其中:
      • Facade知道哪些子系统类负责处理请求。
      • Subsystem classes实现子系统的功能;处理有Facade 对象指派的任务;没有Facade 的任何相关信息。
  • 3)适用性
    • Facade模式适用于:
      • 要为一个复杂子系统提供一个简单接口时,子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类,这使得子系统更具有可重用性,也更容易对子系统进行定制,但也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的默认视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过Facade层。
      • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
      • 当需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,则可以让它们仅通过Facade进行通信,从而简化了它们之间的依赖关系。

3.6 Flyweight(享元)

  • 1)意图
    • 运用共享技术有效地支持大量细粒度的对象。
  • 2)结构
    • 享元模式的结构如图所示。
    • 其中:
      • Flyweight描述一个接口,通过这个接口 Flyweight可以接受并作用于外部状态。
      • ConcreteFlyweight实现Flyweight接口,并为内部状态(如果有)增加存储空间。
      • ConcreteFlyweight 对象必须是可共享的。它所存储的状态必须是内部的,即它必须独立于ConcreteFlyweight对象的场景。
      • 并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight 对象作为子结点。
      • FlyweightFactory创建并管理Flyweight对象;确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者在不存在时创建一个实例。
      • Client维持一个对Flyweight的引用;计算或存储一个或多个Flyweight的外部状态。
  • 3)适用性
    • Flyweight模式适用于:
      • 一个应用程序使用了大量的对象。
      • 完全由于使用大量的对象,造成很大的存储开销。
      • 对象的大多数状态都可变为外部状态。
      • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
      • 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,所以对于概念上明显有别的对象,标识测试将返回真值。

3.7 Proxy(代理)

  • 1)意图
    • 为其他对象提供一种代理以控制对这个对象的访问。
  • 2)结构
    • 代理模式的结构如图所示。
    • 其中:
      • Proxy保存一个引用使得代理可以访问实体;提供一个与Subject 的接口相同的接口,使代理可以用来代替实体;控制对实体的存取,并可能负责创建和删除它;其他功能依赖于代理的类型:Remote Proxy负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求;Virtual Proxy可以缓存实体的附加信息,以便延迟对它的访问:Protection Proxy检查调用者是否具有实现一个请求所必需的访问权限。
      • Subiect 定义RealSubject 和Proxy 的共用接口,这样就在任何使用RealSubject 的地方都可以使用Proxy。
      • RealSubject 定义Proxy所代表的实体。
  • 3)适用性
    • Proxy模式适用于在需要比较通用和复杂的对象指针代替简单的指针的时候,常见情况有:
      • 远程代理(Remote Proxy)为一个对象在不同地址空间提供据不代表。
      • 虚代理(Virtual Proxy)根据需要创建开销很大的对象。
      • 保护代理(Protection Proxy)控制对原始对象的访问,用于对象应该有不同的访问权限的时候。
      • 只能指引(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。典型用途包括:对指向实际对象的引用计数,这样当该对象没有引用时,可以被自动释放;当第一次引用一个持久对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

3.8 结构型模式比较

  • Adapter模式和Bridge模式具有一些功能特征,都给另一个对象提供了一定程度上的间接性,因而有利于系统的灵活性,另外都涉及从自身以外的一个接口向这个对象转发请求。Adapter模式主要是为解决两个已有接口之间不匹配的问题,不考虑这些接口是怎样实现的,也不考虑它们各自可能会如何演化。这种方式不需要对两个独立设计的类中的任何一个进行重新设计,就能够使它们协同工作。Bridge模式则对抽象接口与它的(可能是多个)实现部分进行桥接。虽然这一模式运行使用者修改实现它的类,但是它仍然为用户提供了一个稳定的接口,也会在系统演化时适应新的实现。Adapter模式和Bridge模式通常被用于软件生命周期的不同阶段,针对不同的问题。Adapter模式在类已经设计好后实施;而Bridge模式在设计类之前实施。
  • Composite模式和Decorator模式具有类似的结构,说明它们都是基于递归组合来组织可变数目的对象。Decorator旨在能够不需要生成子类即可给对象添加职责,这避免了静态实现所有功能组合而导致子类急剧增加。Composite旨在构造类,使多个相关的对象能够以统一的方式处理,而多重对象可以被当作一个对象来处理,重点在于表示。两者通常协同使用。
  • Decorator模式和Proxy模式都描述了怎样为对象提供一定程度上的间接引用。Proxy模式构成一个对象并为用户提供一致的接口,与Decorator模式不同的是,Proxy模式不能动态地添加或分离性质,也不是为递归组合而设计的,它强调一种关系(Proxy与它的实体之间的关系),这种关系可以静态地表达。其目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如,实体在远程设备上,访问受到限制或者实体是持久存储的。在Proxy模式中,实体定义了关键功能,而Proxy提供(或拒绝)对它的访问。在Decorator模式中,组件仅提供了部分功能,而一个或多个Decorator负责完成其他功能。Decorator模式适用于编译时不能(至少不方便)确定对象的全部功能的情况。