设计模式

83 阅读30分钟

什么是设计模式

一般而言,一个模式有四个基本要素:

模式名(pattern name) 

一个助记名,它用一两个词来描述模式的问题、解决方案和效果。

命名一个新的模式增加了我们的设计词汇。设计模式允许我们在较高的抽象层次上进行设计。

问题(problem)

描述了应该在何时使用模式。

它解释了设计问题和问题存在的前因后果。它可能描述了特定的设计问题​,也可能描述了导致不灵活设计的类或对象结构。通常问题部分会包括使用模式必须满足的一系列先决条件。

解决方案(solution)

描述了设计的组成成分、它们之间的相互关系及各自的职责和协作方式。

模式就像一个模板,可应用于多种不同场合,所以解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。

效果(consequence)

描述了模式应用的效果及使用模式应权衡的问题。

软件效果大多关注对时间和空间的衡量,模式效果包括它对系统的灵活性、扩充性或可移植性的影响,显式地列出这些效果对理解和评价这些模式很有帮助。

综上所述,设计模式是对用来在特定场景下解决一般设计问题的类和相互通信的对象的描述。

如何描述设计模式

为了达到设计复用,除了图形符号以外,还必须同时记录设计产生的决定过程、选择过程和权衡过程。

用统一的格式描述设计模式,每一个模式根据以下模板被分成若干部分,模板具有统一的信息描述结构。

模式名和分类

模式名简洁地描述了模式的本质。一个好的名字非常重要,因为它将成为你的设计词汇表中的一部分。

意图

意图是回答下列问题的简单陈述:设计模式是做什么的?它的基本原理和意图是什么?它解决的是什么样的特定设计问题?

别名

模式的其他名称。

动机

用以说明一个设计问题以及如何用模式中的类、对象来解决该问题的特定情景。该情景会帮助你理解随后对模式更抽象的描述。

适用性

什么情况下可以使用该设计模式?该模式可用来改进哪些不良设计?你怎样识别这些情况?

结构

采用基于对象建模技术的表示法对模式中的类进行图形描述。也使用了交互图来说明对象之间的请求序列和协作关系。

参与者

指设计模式中的类和/或对象以及它们各自的职责。

协作

模式的参与者怎样协作以实现它们的职责。

效果

模式怎样支持它的目标?使用模式的效果和所需做的权衡是什么?系统结构的哪些方面可以独立改变?

实现

实现模式时需要知道的一些提示、技术要点及应避免的缺陷,以及是否存在某些特定于实现语言的问题。

代码示例

用来说明怎样用编程语言实现该模式的代码片段。

已知应用

实际系统中发现的模式的例子。每个模式至少包括两个不同领域的实例。

相关模式

与这个模式紧密相关的模式有哪些?其间重要的不同之处是什么?这个模式应与哪些其他模式一起使用?

设计模式的编目

Abstract Factory

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

Adapter

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

Bridge

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

Builder

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

Chain of Responsibility

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

Command

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

Composite

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

Decorator

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

Facade

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

Factory Method

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

Flyweight

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

Interpreter

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

Iterator

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

Mediator

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

Memento

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

Observer

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

Prototype

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

Proxy

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

Singleton

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

State

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

Strategy

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

Template Method

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

Visitor

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

组织编目

目的准则,即模式是用来完成什么工作的。

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

image.png

根据模式的“相关模式”部分所描述的它们怎样互相引用来组织设计模式。

image.png

如何解决设计问题

寻找合适的对象

面向对象程序由对象组成,对象包括数据和对数据进行操作的过程,过程通常称为方法操作。对象在收到客户请求(或消息)后,执行相应的操作。

客户请求是使对象执行操作的唯一方法,操作又是对象改变内部数据的唯一方法。由于这些限制,对象的内部状态是被封装的,它不能被直接访问,它的表示对于对象外部是不可见的。

面向对象设计最困难的部分是将系统分解成对象集合。因为要考虑许多因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等,它们都影响着系统的分解,并且这些因素通常还是互相冲突的。

设计的许多对象来源于现实世界的分析模型。但是,设计结果所得到的类通常在现实世界中并不存在。严格反映当前现实世界的模型并不能产生也能反映将来世界的系统。设计中的抽象对于产生灵活的设计是至关重要的。

设计模式帮你确定并不明显的抽象和描述这些抽象的对象。例如,描述过程或算法的对象现实中并不存在,但它们却是设计的关键部分。这些对象在分析阶段,甚至在设计阶段的早期并不存在,后来为使设计更灵活、复用性更好才将它们发掘出来。

决定对象的粒度

对象在大小和数目上变化极大。它们能表示下至硬件或上至整个应用的任何事物。

Facade 模式描述了怎样用对象表示完整的子系统,Flyweight 模式描述了如何支持大量的最小粒度的对象。其他一些设计模式描述了将一个对象分解成许多小对象的特定方法。Abstract Factory 和 Builder 产生那些专门负责生成其他对象的对象。Visitor 和 Command 生成的对象专门负责实现对其他对象或对象组的请求。

指定对象接口

对象声明的每一个操作指定操作名、作为参数的对象和返回值,这就是所谓的操作的型构(signature)。对象操作所定义的所有操作型构的集合被称为该对象的接口(interface)。对象接口描述了该对象所能接受的全部请求的集合,任何匹配对象接口中型构的请求都可以发送给该对象。

类型(type)是一个用来标识特定接口的名字。一个对象可以有许多类型,并且不同的对象可以共享同一个类型。对象接口的某部分可以用某个类型来刻画,而其他部分则可用其他类型刻画。两个类型相同的对象只需要共享它们的部分接口。接口可以包含其他接口作为子集。当一个类型的接口包含另一个类型的接口时,我们就说它是另一个类型的子类型(subtype),而称另一个类型为它的超类型(supertype)。我们常说子类型继承了它的超类型的接口。

在面向对象系统中,接口是基本的组成部分。对象只有通过它们的接口才能与外部交流,如果不通过对象的接口就无法知道对象的任何事情,也无法请求对象做任何事情。对象接口与其功能实现是分离的,不同对象可以对请求做不同的实现,也就是说,两个有相同接口的对象可以有完全不同的实现。

当给对象发送请求时,所引起的具体操作既与请求本身有关又与接受对象有关。支持相同请求的不同对象可能对请求激发的操作有不同的实现。发送给对象的请求和它的相应操作在运行时的连接就称为动态绑定(dynamic binding)。

动态绑定是指发送的请求直到运行时才受你的具体实现的约束。因而,在知道任何有正确接口的对象都将接受此请求时,你可以写一个一般的程序,它期待着那些具有该特定接口的对象。进一步讲,动态绑定允许你在运行时彼此替换有相同接口的对象。这种可替换性就称为多态(polymorphism),它是面向对象系统中的核心概念之一。多态允许客户对象仅要求其他对象支持特定接口,除此之外对其假设几近于无。多态简化了客户的定义,使得对象间彼此独立,并可以在运行时动态改变它们相互的关系。

设计模式通过确定接口的主要组成成分及经接口发送的数据类型来帮助你定义接口。设计模式也许还会告诉你接口中不应包括哪些东西。Memento 模式是一个很好的例子,它描述了怎样封装和保存对象内部的状态,以便一段时间后对象能恢复到这一状态。它规定了 Memento 对象必须定义两个接口:一个允许客户保持和复制 memento 的限制接口,一个只有原对象才能使用的用来储存和提取 memento 中状态的特权接口。

设计模式也指定了接口之间的关系。特别是,它们经常要求一些类具有相似的接口,或它们对一些类的接口做了限制。例如,Decorator 和 Proxy 模式分别要求 Decorator 和 Proxy 对象的接口与被修饰的对象和受委托的对象一致。而 Visitor 模式中,Visitor 接口必须反映出 visitor 能访问的对象的所有类。

描述对象的实现

对象的实现是由它的类决定的,类指定了对象的内部数据和表示,也定义了对象所能完成的操作。

基于 OMG 的表示法,将类描述成一个矩形,其中的类名以黑体表示。类所定义的任何数据在类名下面,以常规字体表示。操作都在类所定义的任何数据的下面。类名与数据之间以及数据与操作之间用横线分隔。返回类型和实例变量类型是可选的,因为我们并未假设一定要用具有静态类型的实现语言。

image.png

对象通过实例化类来创建,此对象被称为该类的实例。当实例化类时,要给对象的内部数据(由实例变量组成)分配存储空间,并将操作与这些数据联系起来。对象的许多类似实例是由实例化同一个类来创建的。

下图中的虚箭头线表示一个类实例化另一个类的对象,箭头指向被实例化的对象的类。

image.png

新的类可以由已存在的类通过类继承(class inheritance)来定义。当子类(subclass)继承父类(parent class)时,子类包含了父类定义的所有数据和操作。子类的实例对象包含所有子类和父类定义的数据,且它们能完成子类和父类定义的所有操作。使用空心三角形箭头表示,箭头从子类指向父类,如下图所示。

image.png

抽象类(abstract class)的主要目的是为它的子类定义公共接口。抽象类将把它的部分或全部操作的实现延迟到子类中,因此,抽象类不能被实例化。在抽象类中定义却没有实现的操作被称为抽象操作(abstract operation)。非抽象类称为具体类(concrete class)。

子类能够改进和重新定义它们父类的操作。更具体地说,类能够重定义(override)父类定义的操作,重定义使得子类能接管父类对请求的处理操作。类继承允许你只需要简单地扩展其他类就可以定义新类,从而可以很容易地定义具有相近功能的对象族。

抽象类的类名以斜体表示,以与具体类相区别。抽象操作也用斜体表示。图中可以包括实现操作的伪代码,如果这样,则代码将出现在带有摺角的框中,并用虚线将该摺角框与代码所实现的操作相连,图示如下。

image.png

类继承与接口继承

理解对象的类(class)与对象的类型(type)之间的差别非常重要。

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

当然,对象的类和类型是有紧密关系的。因为类定义了对象所能执行的操作,也定义了对象的类型。当我们说一个对象是一个类的实例时,即指该对象支持类所定义的接口。

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

很多设计模式依赖于这种差别。例如,Chain of Responsibility 模式中的对象必须有一个公共的类型,但一般情况下它们不具有公共的实现。在 Composite 模式中,构件定义了一个公共的接口,但 Composite 通常定义一个公共的实现。Command、Observer、State 和 Strategy 通常纯粹作为接口的抽象类来实现。

对接口编程

类继承是一个通过复用父类功能而扩展应用功能的基本机制。它允许你根据旧对象快速定义新对象。它允许你从已存在的类中继承所需要的绝大部分功能,从而几乎无须任何代价就可以获得新的实现。

然而,实现的复用只是成功的一半,继承所拥有的定义具有相同接口的对象族的能力也是很重要的(通常可以从抽象类来继承)​。这是为什么?因为多态依赖于这种能力。

当继承被恰当使用时,所有从抽象类导出的类将共享该抽象类的接口。这意味着子类仅仅添加或重定义操作,而没有隐藏父类的操作。这时,所有的子类都能响应抽象类接口中的请求,从而子类的类型都是抽象类的子类型。

只根据抽象类中定义的接口来操纵对象有以下两个好处:

  1. 客户无须知道他们使用对象的特定类型,只需要知道对象有客户所期望的接口。
  2. 客户无须知道他们使用的对象是用什么类来实现的,只需要知道定义接口的抽象类。

这将极大地减少子系统实现之间的相互依赖关系,也产生了可复用的面向对象设计的如下原则:针对接口编程,而不是针对实现编程。

不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口。

当你不得不在系统的某个地方实例化具体的类(即指定一个特定的实现)时,创建型模式(Abstract Factory、Builder、Factory Method、Prototype 和 Singleton)可以帮你。通过抽象对象的创建过程,这些模式提供不同的方式以在实例化时建立接口和实现的透明连接。创建型模式确保你的系统是采用针对接口的方式,而不是针对实现的方式而书写的。

运用复用机制

理解对象、接口、类和继承之类的概念对大多数人来说并不难,问题的关键在于如何运用它们写出灵活的、可复用的软件。

继承和组合

面向对象系统中功能复用的两种最常用技术是类继承和对象组合(object composition)。

类继承允许你根据其他类的实现来定义一个类的实现。这种通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言的:在继承方式中,父类的内部细节对子类可见。

新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。

继承和组合各有优缺点。类继承是在编译时静态定义的,且可直接使用,因为程序设计语言直接支持类继承。类继承可以较方便地改变被复用的实现。当一个子类重定义一些而不是全部操作时,它也能影响它所继承的操作,只要在这些操作中调用了被重定义的操作。

但是类继承也有一些不足之处。首先,因为继承在编译时就定义了,所以无法在运行时改变从父类继承的实现。更糟的是,父类通常至少定义了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性”。子类中的实现与它的父类有如此紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。

当你需要复用子类时,实现上的依赖性就会产生一些问题。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。一个可用的解决方法就是只继承抽象类,因为抽象类通常提供较少的实现。

对象组合是通过获得对其他对象的引用而在运行时动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。这还会产生良好的结果:因为对象只能通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系。

对象组合对系统设计还有另一个作用,即优先使用对象组合有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。另外,基于对象组合的设计会有更多的对象(而有较少的类)​,且系统的行为将依赖于对象间的关系而不是被定义在某个类中。

面向对象设计的第二个原则:优先使用对象组合,而不是类继承。

理想情况下,你不应为获得复用而去创建新的构件。你应该只使用对象组合技术,通过组装已有的构件就能获得你需要的功能。但是事实很少如此,因为可用构件的集合实际上并不足够丰富。使用继承的复用使得创建新的构件要比组装旧的构件来得容易。这样,继承和对象组合常一起使用。

经验表明:设计者往往过度使用了继承这种复用技术。但依赖于对象组合技术的设计却有更好的复用性(或更简单)​。设计模式中一再使用对象组合技术。

委托

委托(delegation)是一种组合方法,它使组合具有与继承同样的复用能力。在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者(delegate)。这类似于子类将请求交给它的父类处理。使用继承时,被继承的操作总能引用接受请求的对象。委托方式为了得到同样的效果,接受请求的对象将自己传给被委托者(代理者)​,使被委托的操作可以引用接受请求的对象。

下面的图显示了窗口类将它的 Area 操作委托给一个矩形实例。

image.png

箭头线表示一个类对另一个类实例的引用关系。引用名是可选的,本例为“rectangle”​。

委托的主要优点在于它便于运行时组合对象操作以及改变这些操作的组合方式。假定矩形对象和圆对象有相同的类型,我们只需要简单地用圆对象替换矩形对象,得到的窗口就是圆形的。

委托与那些通过对象组合取得软件灵活性的技术一样,具有如下不足之处:动态的、高度参数化的软件比静态软件更难于理解。还有运行低效问题,不过从长远来看人的低效才是更主要的。只有当委托使设计比较简单而不是更复杂时,它才是好的选择。要给出一个能确切告诉你什么时候可以使用委托的规则是很困难的。因为委托可以得到的效率是与上下文有关的,并且还依赖于你的经验。委托最适用于符合特定程式的情形,即标准模式的情形。

有一些模式使用了委托,如State、Strategy 和 Visitor。在 State 模式中,一个对象将请求委托给一个描述当前状态的 State 对象来处理。在 Strategy 模式中,一个对象将一个特定的请求委托给一个描述请求执行策略的对象,一个对象只会有一个状态,但它对不同的请求可以有许多策略。这两个模式的目的都是通过改变受托对象来改变委托对象的行为。在 Visitor 中,对象结构的每个元素上的操作总是被委托到 Visitor 对象。

委托是对象组合的特例。它告诉你对象组合作为一个代码复用机制可以替代继承。

运行时和编译时的结构

一个面向对象程序运行时的结构通常与它的代码结构相差较大。代码结构在编译时就被确定下来了,它由继承关系固定的类组成。而程序的运行时结构是由快速变化的通信对象网络组成的。

聚合意味着一个对象拥有另一个对象或对另一个对象负责。一般我们称一个对象包含另一个对象或者是另一个对象的一部分。聚合意味着聚合对象和其所有者具有相同的生命周期。

关联意味着一个对象仅仅知道另一个对象。关联的对象可能请求彼此的操作,但是它们不为对方负责。关联是一种比聚合要弱的关系,它只标识了对象间较松散的耦合关系。

普通的箭头线表示相识,尾部带有菱形的箭头线表示聚合:

image.png

聚合和关联很容易混淆,因为它们通常以相同的方法实现。从根本上讲,是聚合还是关联是由你的意图而不是显式的语言机制决定的。尽管它们之间的区别在编译时的结构中很难看出来,但这些区别还是很大的。聚合关系使用较少且比关联关系更持久;而关联关系则出现频率较高,但有时只存在于一个操作期间,关联也更具动态性,使得它在源代码中更难被辨别出来。

许多设计模式(特别是那些属于对象范围的)显式地记述了编译时和运行时结构的差别。Composite 和 Decorator 对于构造复杂的运行时结构特别有用。Observer 也与运行时结构有关,但这些结构对于不了解该模式的人来说是很难理解的。Chain of Responsibility 也产生了继承所无法展现的通信模式。总之,只有理解了模式,你才能清楚代码中的运行时结构。

设计应支持变化

获得最大限度复用的关键在于对新需求和已有需求发生变化时的预见性,要求你的系统设计能够相应地改进。

为了设计适应这种变化且具有健壮性的系统,你必须考虑系统在它的生命周期内会发生怎样的变化。一个不考虑系统变化的设计在将来就有可能需要重新设计。

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

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

1、通过显式地指定一个类来创建对象

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

设计模式:Abstract Factory,Factory Method,Prototype。

2、对特殊操作的依赖

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

设计模式:Chain of Resposibility,Command。

3、对硬件和软件平台的依赖

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

设计模式:Abstract Factory,Bridge。

4、对对象表示或实现的依赖

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

设计模式:Abstract Factory,Bridge,Memento,Proxy。

5、算法依赖

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

设计模式:Builder,Iterator,Strategy,Template Method,Visitor。

6、紧耦合

紧耦合的类很难独立地被复用,因为它们是互相依赖的。紧耦合产生单块的系统,要改变或删掉一个类,你必须理解和改变其他许多类。这样的系统是一个很难学习、移植和维护的密集体。设计模式使用抽象耦合和分层技术来提高系统的松散耦合性。

设计模式:Abstract Factory,Command,Facade,Mediator,Observer,Chain of Responsibility。

7、通过生成子类来扩充功能

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

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

设计模式:Bridge,Chain of Responsibility,Composite,Decorator,Observer,Strategy。

8、不能方便地对类进行修改

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

设计模式:Adapter,Decorator,Visitor。

怎么选择设计模式

  1. 考虑设计模式是怎样解决设计问题的
  2. 浏览模式的意图部分
  3. 研究模式怎样互相关联
  4. 研究目的相似的模式
  5. 检查重新设计的原因
  6. 考虑你的设计中哪些是可变的。封装变化

image.png

怎么使用设计模式

关于设计模式,如果不提一下它们的使用限制,那么关于怎样使用它们的讨论就是不完整的。设计模式不能够随意使用。通常你通过引入额外的间接层次获得灵活性和可变性的同时,也使设计变得更复杂和/或牺牲了一定的性能。一个设计模式只有当它提供的灵活性是真正需要的时候,才有必要使用。当衡量一个模式的得失时,它的效果部分是最能提供帮助的。