设计模式可复用面向对象软件的基础 第3章 创建型模式

更新时间:2023-04-20 21:52:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

第3章创建型模式

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

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

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

有时创建型模式是相互竞争的。例如,在有些情况下 P r o t o t y p e(3 . 4)或Abstract Factory (3 . 1)用起来都很好。而在另外一些情况下它们是互补的: B u i l d e r(3 . 2)可以使用其他模式去实现某个构件的创建。P r o t o t y p e(3 . 4)可以在它的实现中使用 S i n g l e t o n(3 . 5)。 因为创建型模式紧密相关,我们将所有5个模式一起研究以突出它们的相似点和相异点。 我们也将举一个通用的例子—为一个电脑游戏创建一个迷宫—来说明它们的实现。这个迷 宫和游戏将随着各种模式不同而略有区别。有时这个游戏将仅仅是找到一个迷宫的出口;在 这种情况下,游戏者可能仅能见到该迷宫的局部。有时迷宫包括一些要解决的问题和要战胜 的危险,并且这些游戏可能会提供已经被探索过的那部分迷宫地图。

我们将忽略许多迷宫中的细节以及一个迷宫游戏中有一个还是多个游戏者。我们仅关注 迷宫是怎样被创建的。我们将一个迷宫定义为一系列房间,一个房间知道它的邻居;可能的 邻居要么是另一个房间,要么是一堵墙、或者是到另一个房间的一扇门。

类R o o m、D o o r和Wa l l定义了我们所有的例子中使用到的构件。我们仅定义这些类中对创 建一个迷宫起重要作用的一些部分。我们将忽略游戏者、显示操作和在迷宫中四处移动操作, 以及其他一些重要的却与创建迷宫无关的功能。

下页图表示了这些类之间的关系。每一个房间有四面,我们使用 C + +中的枚举类型D i r e c t i o n来指定房间的东南西北:

enum Direction {North, South, East, West};

S m a l l t a l k的实现使用相应的符号来表示这些方向。

类M a p S i t e是所有迷宫组件的公共抽象类。为简化例子, M a p S i t e仅定义了一个操作E n t e r, 它的含义决定于你在进入什么。如果你进入一个房间,那么你的位置会发生改变。如果你试 图进入一扇门,那么这两件事中就有一件会发生:如果门是开着的,你进入另一个房间。如果门是关着的,那么你就会碰壁。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

E n t e r为更加复杂的游戏操作提

供了一个简单基础。例如,如果你在

一个房间中说“向东走”,游戏只能确定

直接在东边的是哪一个 M a p S i t e并

对它调用E n t e r。特定子类的E n t e r

操作将计算出你的位置是发生改变,

还是你会碰壁。在一个真正的游戏中,

E n t e r可以将移动的游戏者对象作为

一个参数。

R o o m是M a p S i t e的一个具体

的子类,而 M a p S i t e定义了迷宫中

构件之间的主要关系。 R o o m有指向

其他M a p S i t e对象的引用,并保存一

个房间号,这个数字用来标识迷宫中

的房间。

下面的类描述了一个房间的每一面所出现的

墙壁或门。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

我们不仅需要知道迷宫的各部分,还要定义一个用来表示房间集合的 M a z e类。用 R o o m N o操作和给定的房间号,M a z e就可以找到一个特定的房间。

R o o m N o可以使用线形搜索、 h a s h表、甚

至一个简单数组进行一次查找。但我们在此处并不

考虑这些细节,而是将注意力集中于如何指定一个

迷宫对象的构件上。

我们定义的另一个类是 M a z e G a m e,由它

来创建迷宫。一个简单直接的创建迷宫的方法是使

用一系列操作将构件增加到迷宫中,然后连接它们。

例如,下面的成员函数将创建一个迷宫,这个迷宫由两个房间和它们之间的一扇门组成:

考虑到这个函数所做的仅是创建一个有两个房间的迷宫,它是相当复杂的。显然有办法使它变得更简单。例如, R o o m的构造器可以提前用墙壁来初始化房间的每一面。但这仅仅是将代码移到了其他地方。

这个成员函数真正的问题不在于它的大

小而在于它不灵活。它对迷宫的布局进

行硬编码。改变布局意味着改变这个成

员函数,或是重定义它—这意味着重新

实现整个过程—或是对它的部分进行改

变—这容易产生错误并且不利于重用。

创建型模式显示如何使得这个设计更灵

活,但未必会更小。特别是,它们将便

于修改定 义一个迷宫构件的类。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

假设你想在一个包含(所有的东西)施了魔法的迷宫的新游戏中重用一个已有的迷宫布局。施了魔法的迷宫游戏有新的构件,像 D o o r N e e d i n g S p e l l,它是一扇仅随着一个咒语才能被锁上和打开的门;以及 E n c h a n t e d R o o m,一个可以有不寻常东西的房间,比如魔法钥匙或是咒语。你怎样才能较容易的改变 C r e a t e M a z e以让它用这些新类型的对象创建迷宫呢?

这种情况下,改变的最大障碍是对被实例化的类进行硬编码。创建型模式提供了多种不同方法从实例化它们的代码中除去对这些具体类的显式引用:

如果C r e a t e M a z e调用虚函数而不是构造器来创建它需要的房间、墙壁和门,那么你可以创建一个 M a z e G a m e的子类并重定义这些虚函数,从而改变被例化的类。这一方法是 Factory Method(3 . 3)模式的一个例子。

如果传递一个对象给 C r e a t e M a z e作参数来创建房间、墙壁和门,那么你可以传递不同的参数来改变房间、墙壁和门的类。这是 Abstract Factory(3 . 1)模式的一个例子。

如果传递一个对象给 C r e a t e M a z e,这个对象可以在它所建造的迷宫中使用增加房间、墙壁和门的操作,来全面创建一个新的迷宫,那么你可以使用继承来改变迷宫的一些部分或该迷宫被建造的方式。这是 B u i l d e r(3 . 2)模式的一个例子。

如果C r e a t e M a z e由多种原型的房间、墙壁和门对象参数化,它拷贝并将这些对象增加到迷宫中,那么你可以用不同的对象替换这些原型对象以改变迷宫的构成。这是 P r o t o t y p e

(3 . 4)模式的一个例子。

剩下的创建型模式,S i n g l e t o n(3 . 5),可以保证每个游戏中仅有一个迷宫而且所有的游戏对象都可以迅速访问它—不需要求助于全局变量或函数。 S i n g l e t o n也使得迷宫易于扩展或替换,且不需变动已有的代码。

3.1 ABSTRACT FACTORY(抽象工厂)—对象创建型模式

1. 意图

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

2. 别名 K i t

3. 动机

考虑一个支持多种视感( l o o k - a n d - f e e l)标准的用户界面工具包,例如 M o t i f和

Presentation Manager。不同的视感风格为诸如滚动条、窗口和按钮等用户界面“窗口组件”定义不同的外观和行为。为保证视感风格标准间的可移植性,一个应用不应该为一个特定的视感外观硬编码它的窗口组件。在整个应用中实例化特定视感风格的窗口组件类将使得以后很难改变视感风格。

为解决这一问题我们可以定义一个抽象的 Wi d g e t F a c t o r y类,这个类声明了一个用来创建每一类基本窗口组件的接口。每一类窗口组件都有一个抽象类,而具体子类则实现了窗口组件的特定视感风格。对于每一个抽象窗口组件类, Wi d g e t F a c t o r y接口都有一个返回新窗口组件对象的操作。客户调用这些操作以获得窗口组件实例,但客户并不知道他们正在使用的是哪些具体类。这样客户就

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

不依赖于一般的视感风格,如下页图所示。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

每一种视感标准都对应于一个具体的 Wi d g e t F a c t o r y子类。每一子类实现那些用于创建合适视感风格的窗口组件的操作。例如, M o t i f Wi d g e t F a c t o r y的C r e a t e S c r o l l B a r操作实例化并返回一个M o t i f滚动条,而相应的 P M Wi d g e t F a c t o r y操作返回一个Presentation Manager的滚动条。客户仅通过 Wi d g e t F a c t o r y接口创建窗口组件,他们并不知道哪些类实现了特定视感风格的窗口组件。换言之,客户仅与抽象类定义的接口交互,而不使用特定的具体类的接口。

Wi d g e t F a c t o r y也增强了具体窗口组件类之间依赖关系。一个 M o t i f的滚动条应该与M o t i f按钮、M o t i f正文编辑器一起使用,这一约束条件作为使用 M o t i f Wi d g e t F a c t o r y的结果被自动加上。

4. 适用性在以下情况可以使用 Abstract Factory模式

一个系统要独立于它的产品的创建、组合和表示时。

一个系统要由多个产品系列中的一个来配置时。

当你要强调一系列相关的产品对象的设计以便进行联合使用时。

当你提供一个产品类库,而只想显示它们的接口而不是实现时。

5. 结构

6. 参与者

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

此模式的结构如下图所示。

A b s t r a c t F a c t o r y ( Wi d g e t F a c t o r y )

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

—声明一个创建抽象产品对象的操作接口。

C o n c r e t e F a c t o r y ( M o t i f Wi d g e t F a c t o r y,P M Wi d g e t F a c t o r y )—实现创建具体产品对象的操作。

A b s t r a c t P r o d u c t ( Wi n d o w s,S c r o l l B a r )—为一类产品对象声明一个接口。 C o n c r e t e P r o d u c t ( M o t i f Wi n d o w,M o t i f S c r o l l B a r )—定义一个将被相应的具体工厂创建的产品对象。—实现 A b s t r a c t P r o d u c t接口。

7. 协作

通常在运行时刻创建一个 C o n c r e t e F a c t r o y类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,客户应使用不同的具体工厂。

AbstractFactory将产品对象的创建延迟到它的 C o n c r e t e F a c t o r y子类。

8. 效果 A b s t r a c t F a c t o r y模式有下面的一些优点和缺点:

1) 它分离了具体的类 Abstract Factory模式帮助你控制一个应用创建的对象的类。因为一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。客户通过它们的抽象接口操纵实例。产品的类名也在具体工厂的实现中被分离;它们不出现在客户代码中。

2) 它使得易于交换产品系列一个具体工厂类在一个应用中仅出现一次—即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。在我们的用户界面的例子中,我们仅需转换到相应的工厂对象并重新创建接口,就可实现从M o t i f窗口组件转换为Presentation Manager窗口组件。

3) 它有利于产品的一致性当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要。而 A b s t r a c t F a c t o r y很容易实现这一点。

4) 难以支持新种类的产品难以扩展抽象工厂以生产新种类的产品。这是因为 A b s t r a c t F a c t o r y接口确定了可以被创建的产品集合。支持新种类的产品就需要扩展该工厂接口,这将涉及A b s t r a c t F a c t o r y类及其所有子类的改变。我们会在实现一节讨论这个问题的一个解决办法。

9. 实现下面是实现Abstract Factor模式的一些有用技术:

1) 将工厂作为单件一个应用中一般每个产品系列只需一个 C o n c r e t e F a c t o r y的实例。因此工厂通常最好实现为一个 S i n g l e t o n(3 . 5)。

2) 创建产品 A b s t r a c t F a c t o r y仅声明一个创建产品的接口,真正创建产品是由 C o n c r e t e P r o d u c t子类实现的。最通常的一个办法是为每一个产品定义一个工厂方法(参见 Factory Method(3 . 3))。一个具体的工厂将为每个产品重定义该工厂方法以指定产品。虽然这样的实现很简单,但它却要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小。

C l i e n t—仅使用由 A b s t r a c t F a c t o r y和A b s t r a c t P r o d u c t类声明的接口。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

如果有多个可能的产品系列,具体工厂也可以使用 P r o t o t y p e(3 . 4)模式来实现。具体工厂使用产品系列中每一个产品的原型实例来初始化,且它通过复制它的原型来创建新的产品。在基于原型的方法中,使得不是每个新的产品系列都需要一个新的具体工厂类。

此处是S m a l l t a l k中实现一个基于原型的工厂的方法。具体工厂在一个被称为 p a r t C a t a l o g的字典中存储将被复制的原型。方法 m a k e:检索该原型并复制它:

make : partName ^ (partCatalog at : partName) copy

具体工厂有一个方法用来向该目录中增加部件。

addPart : partTemplate named : partName partCatalog at : partName put : partTemplate

原型通过用一个符号标识它们,从而被增加到工厂中:

aFactory addPart : aPrototype named : #ACMEWidget

在将类作为第一类对象的语言中(例如 S m a l l t a l k和O b j e c t i v e C),这个基于原型的方法可能有所变化。你可以将这些语言中的一个类看成是一个退化的工厂,它仅创建一种产品。你可以将类存储在一个具体工厂中,这个具体工厂在变量中创建多个具体的产品,这很像原型。这些类代替具体工厂创建了新的实例。你可以通过使用产品的类而不是子类初始化一个具体工厂的实例,来定义一个新的工厂。这一方法利用了语言的特点,而纯基于原型的方法是与语言无关的。

像刚讨论过的 S m a l l t a l k中的基于原型的工厂一样,基于类的版本将有一个唯一的实例变量p a r t C a t a l o g,它是一个字典,它的主键是各部分的名字。 p a r t C a t a l o g存储产品的类而不是存储被复制的原型。方法m a k e:现在是这样:

make : partName ^ (partCatalog at : partName) new

3) 定义可扩展的工厂 A b s t r a c t F a c t o r y通常为每一种它可以生产的产品定义一个操作。产品的种类被编码在操作型构中。增加一种新的产品要求改变 A b s t r a c t F a c t o r y的接口以及所有与它相关的类。一个更灵活但不太安全的设计是给创建对象的操作增加一个参数。该参数指定了将被创建的对象的种类。它可以是一个类标识符、一个整数、一个字符串,或其他任何可以标识这种产品的东西。实际上使用这种方法, A b s t r a c t F a c t o r y只需要一个“ M a k e”操作和一个指示要创建对象的种类的参数。这是前面已经讨论过的基于原型的和基于类的抽象工厂的技术。

C + +这样的静态类型语言与相比,这一变化更容易用在类似于 S m a l l t a l k这样的动态类型语言中。仅当所有对象都有相同的抽象基类,或者当产品对象可以被请求它们的客户安全的强制转换成正确类型时,你才能够在 C + +中使用它。Factory Method(3.3)的实现部分说明了怎样在C + +中实现这样的参数化操作。

该方法即使不需要类型强制转换,但仍有一个本质的问题:所有的产品将返回类型所给定的相同的抽象接口返回给客户。客户将不能区分或对一个产品的类别进行安全的假定。如果一个客户需要进行与特定子类相关的操作,而这些操作却不能通过抽象接口得到。虽然客户可以实施一个向下类型转换( d o w n c a s t)(例如在C + +中用d y n a m i c _ c a s t),但这并不总是可行或安全的,因为向下类型转换可能会失败。这是一个典型的高度灵活和可扩展接口的权衡 折衷。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

10. 代码示例我们将使用Abstract Factory模式创建我们在这章开始所讨论的迷宫。类M a z e F a c t o r y可以创建迷宫的组件。它建造房间、墙壁和房间之间的门。它可以用于一

个从文件中读取迷宫说明图并建造相应迷宫的程序。或者它可以被用于一个随机建造迷宫的程序。建造迷宫的程序将 M a z e F a c t o r y作为一个参数,这样程序员就能指定要创建的房间、墙壁和门等类。

回想一下建立一个由两个房间和它们之间的门组成的小迷宫的成员函数 C r e a t e M a z e。 C r e a

t e M a z e对类名进行硬编码,

这使得很难用不同的组件创

建迷宫。

这里是一个以M a z e F a c t

o r y为参数的新版本的

CreateMaze ,它修改了以上

缺点:

我们创建M a z e F a c t o r y

的子类E n c h a n t e d M a z e F a c t o r y,这是一个创建施了魔法的迷宫的工厂。E n c h a n t e d M a z e F a c t o r y将重定义不同的成员函数并返回 R o o m,Wa l l等不同的子类。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

现在假设我们想生成一个迷宫游戏,在这个游戏里,每个房间中可以有一个炸弹。如果这个炸弹爆炸,它将(至少)毁坏墙壁。我们可以生成一个 R o o m的子类以明了是否有一个炸弹在房间中以及该炸弹是否爆炸了。我们也将需要一个 Wa l l的子类以明了对墙壁的损坏。我们将称这些类为R o o m Wi t h A B o m b和B o m b e d Wa l l。

我们将定义的最后一个类是 B o m b e d M a z e F a c t o r y,它是M a z e F a c t o r y的子类,保证了墙壁是B o m b e d Wa l l类的而房间是R o o m Wi t h A B o m b的。B o m b e d M a z e F a c t o r y仅需重

定义两个函数:

为建造一个包含炸弹的简单迷

宫,我们仅用 B o m b e d M a z

MazeGame game; BombedMazeFactory factory;

g a m e . C r e a t e M a z e ( f a c t o r y ) ; e F a c t o r y调用C r e a t e M a z e。

C r e a t e M a z e也可以接收一个E n c h a n t e d M a z e F a c t o r y实例来建造施了魔法的迷宫。 注意M a z e F a c t o r y仅是工厂方法的一个集合。这是最通常的实现 Abstract Factory模式的方式。同时注意 M a z e F a c t o r y不是一个抽象类;因此它既作为 A b s t r a c t F a c t o r y也作为 C o n c r e t e F a c t o r y。这是 Abstract Factory模式的简单应用的另一个通常的实现。因为 M a z e F a c t o r y是一个完全由工厂方法组成的具体类,通过生成一个子类并重定义需要改变的操作,它很容易生成一个新的 M a z e F a c t o r y。

C r e a t e M a z e使用房间的S e t S i d e操作以指定它们的各面。如果它用一个 B o m b e d M a z e F a c t o r y创建房间,那么该迷宫将由有 B o m b e d Wa l l面的 R o o m Wi t h A B o m b对象组成。如果 R o o m Wi t h A B o m b必须访问一个B o m b e d Wa l l的与特定子类相关的成员,那么它将不得不对它的墙壁引用以进行从 Wa l l *到B o m b e d Wa l l *的转换。只要该参数确实是一个 B o m b e d Wa l l,这个向下类型转换就是安全的,而如果墙壁仅由一个 B o m b e d M a z e F a c t o r y创建就可以保证这一点。

当然,像S m a l l t a l k这样的动态类型语言不需要向下类型转换,但如果它们在应该是 Wa l l的子类的地方遇到一个 Wa l l类可能会产生运行时刻错误。使用 Abstract Factory建造墙壁,通过确定仅有特定类型的墙壁可以被创建,从而有助于防止这些运行时刻错误。

让我们考虑一个S m a l l t a l k版本的M a z e F a c t o r y,它仅有一个以要生成的对象种类为参数的 m a k e操作。此外,具体工厂存储它所创建的产品的类。

首先,我们用S m a l l t a l k写一个等价的C r e a t e M a z e:

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

正如我们在实现一节所讨论, M a z e F a c t o r y仅需一个实例变量 p a r t C a t a l o g来提供一个字典,

这个字典的主键为迷宫组件的类。也回想一下

我们是如何实现 m a k e :方法的:

现在我们可以创建一个 M a z e F a c t o r y并用它来实现 C r e a t e M a z e。我们将用类 M a z e G a m e

的一个方法C r e a t e M a z e F a c t o r y来创

建该工厂。

通过将不同的类与它们的主键相关联,就可以

创建一个 B o m b e d M a z e F a c t o r y或E n

c h a n t e d M a z e F a c t o r y。例如,一个E n c h a n t e d M a z e F a c t o r y可以这样被创建:

11. 已知应用

I n t e r Vi e w使用“K i t”后缀

[ L i n 9 2 ]来表示A b s t r a c t F a

c t o r y类。它定义 Wi d g e t K i t

和D i a l o g K i t抽象工厂来生成与特定视感风格相关的用户界面对象。 I n t e r Vi e w还包括一个L a y o u t K i t,它根据所需要的布局生成不同的组成( c o m p o s i t i o n)对象。例如,一个概念上是水平的布局根据文档的定位(画像或是风景)可能需要不同的组成对象。

E T + + [ W G M 8 8 ]使用Abstract Factory模式以达到在不同窗口系统(例如, X Wi n d o w s和 S u n Vi e w)间的可移植性。 Wi n d o w S y s t e m抽象基类定义一些接口,来创建表示窗口系统资源的对象(例如 M a k e Wi n d o w、M a k e F o n t、M a k e C o l o r)。具体的子类为某个特定的窗口系统实现这些接口。运行时刻, E T + +创建一个具体 Wi n d o w S y s t e m子类的实例,以创建具体的系统资源对象。

12. 相关模式 A b s t r a c t F a c t o r y类通常用工厂方法( Factory Method(3 . 3))实现,但它们也可以用

P r o t o t y p e实现。一个具体的工厂通常是一个单件( S i n g l e t o n(3 . 5))。

3.2 BUILDER(生成器)—对象创建型模式

1. 意图

2. 动机

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

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

一个RT F(Rich Text Format)文档交换格式的阅读器应能将 RT F转换为多种正文格式。该阅读器可以将 RT F文档转换成普通A S C I I文本或转换成一个能以交互方式编辑的正文窗口组件。但问题在于可能转换的数目是无限的。因此要能够很容易实现新的转换的增加,同时却不改变RT F阅读器。

一个解决办法是用一个可以将 RT F转换成另一种正文表示的 Te x t C o n v e r t e r对象配置这个 RT F R e a d e r类。当RT F R e a d e r对RT F文档进行语法分析时,它使用 Te x t C o n v e r t e r去做转换。无论何时 RT F R e a d e r识别了一个 RT F标记(或是普通正文或是一个 RT F控制字),它都发送一个请求给Te x t C o n v e r t e r去转换这个标记。 Te x t C o n v e r t e r对象负责进行数据转换以及用特定格式表示该标记,如下图所示。

Te x t C o n v e r t的子类对不同转换和不同格式进行特殊处理。例如,一个 A S C I I C o n v e r t e r只 负责转换普通文本,而忽略其他转换请求。另一方面,一个 Te X C o n v e r t e r将会为实现对所有 请求的操作,以便生成一个获取正文中所有风格信息的 T E X表示。一个 Te x t Wi d g e t C o n v e r t e r

将生成一个复杂的用户界面对象以便用户浏览和编辑正文。

每种转换器类将创建和装配一个复杂对象的机制隐含在抽象接口的后面。转换器独立于

阅读器,阅读器负责对一个 RT F文档进行语法分析。

B u i l d e r模式描述了所有这些关系。每一个转换器类在该模式中被称为生成器( b u i l d e r), 而阅读器则称为导向器( d i r e c t o r)。在上面的例子中, B u i l d e r模式将分析文本格式的算法(即RT F文档的语法分析程序)与描述怎样创建和表示一个转换后格式的算法分离开来。这使我们可以重

用 RT F R e a d e r的语法分析算法,根据 RT F文档创建不同的正文表示—仅需使用不

同的Te x t C o n v e r t e r的子类配置该RT F R e a d e r即可。

3. 适用性在以下情况使用B u i l d e r模式

当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。

当构造过程必须允许被构造的对象有不同的表示时。

4. 结构

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

此模式结构如下页上图所示。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

5. 参与者

B u i l d e r(Te x t C o n v e r t e r)—为创建一个 P r o d u c t对象的各个部件指定抽象接口。

C o n c r e t e B u i l d e r(A S C I I C o n v e r t e r、Te X C o n v e r t e r、Te x t Wi d g e t C o n v e r t e r)—实现 B u i l d e r的接口以构造和装配该产品的各个部件。—定义并明确它所创建的表示。—提供一个检索产品的接口(例如, G e t A S C I I Te x t和G e t Te x t Wi d g e t)。

Director(RT F R e a d e r)—构造一个使用 B u i l d e r接口的对象。

P r o d u c t(A S C I I Te x t、Te X Te x t、Te x t Wi d g e t)—表示被构造的复杂对象。 C o n c r e t e B u i l d e r创建该产品的内部表示并定义它的装配过程。 —包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

6. 协作

客户创建D i r e c t o r对象,并用它所想要的 B u i l d e r对象进行配置。

一旦产品部件被生成,导向器就会通知生成器。

生成器处理导向器的请求,并将部件添加到该产品中。

客户从生成器中检索产品。下面的交互图说明了 B u i l d e r和D i r e c t o r是如何与一个客户协作的。

7. 效果这里是B u i

l d e r模式的主要效

果:

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

1 )它使你可以改变一个产品的内部表示 B u i l d e r对象提供给导向器一个构造产品的抽象接口。该接口使得生成器可以隐藏这个产品的表示和内部结构。它同时也隐藏了该产品是如何装配的。因为产品是通过抽象接口构造的,你在改变该产品的内部表示时所要做的只是定义一个新的生成器。

2) 它将构造代码和表示代码分开 B u i l d e r模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性。客户不需要知道定义产品内部结构的类的所有信息;这些类是不出现在B u i l d e r接口中的。每个 C o n c r e t e B u i l d e r包含了创建和装配一个特定产品的所有代码。这些代码只需要写一次;然后不同的 D i r e c t o r可以复用它以在相同部件集合的基础上构作不同的 P r o d u c t。在前面的RT F例子中,我们可以为 RT F格式以外的格式定义一个阅读器,比如一个 S G M L R e a d e r,并使用相同的 Te x t C o n v e r t e r生成 S G M L文档的 A S C I I Te x t、Te X Te x t和 Te x t Wi d g e t译本。

3 )它使你可对构造过程进行更精细的控制 B u i l d e r模式与一下子就生成产品的创建型模式不同,它是在导向者的控制下一步一步构造产品的。仅当该产品完成时导向者才从生成器中取回它。因此 B u i l d e r接口相比其他创建型模式能更好的反映产品的构造过程。这使你可以更精细的控制构建过程,从而能更精细的控制所得产品的内部结构。

8. 实现通常有一个抽象的 B u i l d e r类为导向者可能要求创建的每一个构件定义一个操作。这些操

作缺省情况下什么都不做。一个 C o n c r e t e B u i l d e r类对它有兴趣创建的构件重定义这些操作。

这里是其他一些要考虑的实现问题:

1) 装配和构造接口生成器逐步的构造它们的产品。因此 B u i l d e r类接口必须足够普遍,以便为各种类型的具体生成器构造产品。

一个关键的设计问题在于构造和装配过程的模型。构造请求的结果只是被添加到产品中,通常这样的模型就已足够了。在 RT F的例子中,生成器转换下一个标记并将它添加到它已经转换了的正文中。

但有时你可能需要访问前面已经构造了的产品部件。我们在代码示例一节所给出的 M a z e例子中,M a z e B u i l d e r接口允许你在已经存在的房间之间增加一扇门。像语法分析树这样自底向上构建的树型结构就是另一个例子。在这种情况下,生成器会将子结点返回给导向者,然后导向者将它们回传给生成者去创建父结点。

2) 为什么产品没有抽象类通常情况下,由具体生成器生成的产品,它们的表示相差是如此之大以至于给不同的产品以公共父类没有太大意思。在 RT F例子中, A S C I I Te x t和 Te x t Wi d g e t对象不太可能有公共接口,它们也不需要这样的接口。因为客户通常用合适的具体生成器来配置导向者,客户处于的位置使它知道 B u i l d e r的哪一个具体子类被使用和能相应的处理它的产品。

3 )在B u i l d e r中却省的方法为空 C + +中,生成方法故意不声明为纯虚成员函数,而是把它们定义为空方法,这使客户只重定义他们所感兴趣的操作。

9. 代码示例我们将定义一个 C r e a t e M a z e成员函数的变体,它以类 M a z e B u i l d e r的一个生成器对象作为

参数。 M a z e B u i l d e r类定义下面的接口来创建迷宫:

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

该接口可以创建:1)迷宫。2)有一个特定房间号的房间。3)在有号码的房间之间的门。 G e t M a z e操作返回这个迷宫给客户。 M a z e B u i l d e r的子类将重定义这些操作,返回它们所创建的迷宫。

M a z e B u i l d e r的所有建造迷宫的操作缺省时什么也不做。不将它们定义为纯虚函数是为了便于派生类只重定义它们所感兴趣的那些方法。

用M a z e B u i l d e r接

口,我们可以改变C r e a t e

M a z e成员函数,以生成器

作为它的参数。

将这个C r e a t e M a z

e版本与原来的相比,注意

生成器是如何隐藏迷宫的内部表示的—即定义房间、门和墙壁的那些类—以及这些部件是如何组装成最终的迷宫的。有人可能猜测到有一些类是用来表示房间和门的,但没有迹象显示哪个类是用来表示墙壁的。这就使得改变一个迷宫的表示方式要容易一些,因为所有 M a z e B u i l d e r的客户都不需要被改变。

像其他创建型模式一样, B u i l d e r模式封装了对象是如何被创建的,在这个例子中是通过 M a z e B u i l d e r所定义的接口来封装的。这就意味着我们可以重用 M a z e B u i l d e r来创建不同种类的迷

宫。C r e a t e C o m p

l e x M a z e操作给出

了一个例子:

注意M a z e B u

i l d e r自己并不创建迷宫;它的主要目的仅仅是为创建迷宫定义一个接口。它主要为方便起见定义一些空的实现。 M a z e B u i l d e r的子类做实际工作。

子类S t a n d a r d M a z e B u i l d e r是一个创建简单迷宫的实现。它将它正在创建的迷宫放在变量

_ c u r r e n t M a z e中。

本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用

C o m m o n Wa l l是一个功能性操作,它决定两个房间之间的公共墙壁的方位。 S

t a n d a r d M a z e B u i l d e r的构造器只初始化了 _ c u r r e n t M a z e。

B u i l d M a z e实例化一个 M a z e,

它将被其他操作装配并最终返回

给客户(通过 G e t M a z e)。

B u i l d R o o m操作创建一个房间并建

造它周围的墙壁:

为建造一扇两个房间之间的门, S t a n d

a r d M a z e B u i l d e r查找迷宫中的这

两个房间并找到它们相邻的墙:

客户现在可以用C r e a t e M a z e和

S t a n d a r d M a z e B u i l d e r来创

建一个迷宫:

我们本可以将所有的 S t a n d

a r d M a z e B u i l d e r操作放在M

a n d a r d M a z e B u i l d

e r易于从M a z e中分离。

更重要的是,将两者分离

使得你可以有多种 M a z

e B u i l d e r,每一种使用

不同的房间、墙壁和门的

类。 一个更特殊的M a z e B u i l d e r是C o u n t i n g M

a z e B u i l d e r。这个生成器根本不创建迷宫;它仅仅

对已被创建的不同种类的构件进行计数。

a z e中并让每一个 M a z e创建它自身。但将M a z e变得小一些使得它能更容易被理解和修改,而且 S t

本文来源:https://www.bwwdw.com/article/wviq.html

Top