[本文首发于cnblogs 作者:byeyear Email:[email protected] 转载请注明]
本文是关于创建型模式的杂谈,全文的组织结构比较松散,但基本上还是依着原文的编写顺序来谈。
A. 约定
使用”产品“来指代中间产品(如Room、Door、Wall等);
使用”成品“来指代最终的产品(如Maze)。
B. 创建型模式分类
创建型模式可分为两类:类创建型模式和对象创建型模式。
B.1 类创建型模式使用继承改变被实例化的类,例如Factory Method,使用继承改变产品特性;
B.2 对象创建型模式将实例化委托给另一个对象,例如Abstract Factory,将实例化委托给具体的工厂类。
C. MapSite类是产品的公共抽象基类:Enter是这些组件都具有的公共操作。
从直观上(现实角度)来看,Room、Door、Wall这些产品似乎难以有一个公共的基类。可以这样考虑:首先,Room、Door、Wall这三种产品作为Maze的构成要素,应该是平行的;其次,考虑这三种产品有什么样的公共属性,并将其提取出来作为基类。
D. Room一方面继承于MapSite,另一方面又包含四面MapSite指针:同时运用了继承和组合。
E. Maze类是Maze的表示,MazeGame类负责Maze的创建——将Maze的表示和创建分离开来。这有利于将来单独改变Maze的表示或Maze的创建。比如,Maze可以用链表表示,也可以用数组表示,改变其表示方式不会影响到MazeGame类。
F. 原文中所说的”对布局进行硬编码“有两个方面的意思:一是对迷宫结构的硬编码,二是对迷宫构件的硬编码。前者使得增删房间等操作变得困难,后者使得难以采用不同特色的构件如BomedRoom。Builder模式是解决前者的,其他模式都用于解决后者。
G. AbstractFactory
G.1 Client仅适用Abstract Factory提供的接口创建对象,而不使用特定的具体类的接口;实际的对象创建由(继承于Abstract Factory的)Concrete Factory完成。由于各Concrete Factory具有(继承于Abstract Factory的)完全相同的Product生产接口,所以各个Concrete Factory的产品线必须完全一致。这样,如果Abstract Factory需要增加新的产品接口,所有Concrete Factory都必须修改。Abstract Factory模式使得各Product之间强相关。假如你有两个Factory甲和乙,生产三种Product A、B、C,Client要么使用甲A甲B甲C,要么使用乙A乙B乙C,而不能同时使用甲A和乙B。这既是优势也是缺点:这种特性有利于产品的一致性,非常适合于一个系列的产品对象必须一起工作的情况;但对于不同Factory的产品对象要能够互换的情况显然不合适。
G.2 典型的Abstract Factory的例子是支持多种视觉风格的用户界面工具包:界面上的各widget风格必须统一,因此不可能在同一界面上使用多个Widget Factory(不同Concrete Factory的产品不可互换);不同风格的界面总是有相同种类的Widget(不同的Concreate Factory具有相同的产品线)。
G.3 在Abstract Factory中定义产品接口并在Concrete Factory中实现的做法实际上就是Factory Method:将产品的创建延迟到具体的Concrete Factory中。
G.4 原始代码与采用了Abstract Factory模式的代码对比:
原始版本:
Maze* MazeGame::CreateMaze()
{
Maze *aMaze = new Maze();
Room *r1 = new Room(1);
}
修改版本:
Maze* MazeGame::CreateMaze(Factory& factory)
{
Maze *aMaze = factory.MakeMaze();
Room *r1 = factory.MakeRoom(1);
}
和原始版本相比,修改版本不直接生产Product,而是在代码某处new一个Factory出来,由这个Factory生产。
H. Abstract Factory和Factory Method的对比
H.1 Abstract Factory:一般来说,Abstract和Concrete Factory都在“框架”中实现。Client直接使用框架设计者提供的Concrete Factory来创建Product,一般不会去创建新的Factory(Client仅使用Product,而不生产Product)。这是因为Abstract Factory的每次改动都会涉及Concrete Factory,Abstract类和Concrete类是“联动”的,它们不适合分开在不同的设计者层次中实现。
H.2 Factory Method:框架设计者提供Interface,由Client实现之(Client需要自己生产Product)。
H.3 Abstract Factory中,产品系列总是整体替换;
H.4 Factory Method更适合于对已有产品特性进行修正、更改、扩充。
H.5 Abstract Factory中产品和工厂强关联。
H.6 Factory Method中产品和Creator关联不强。比如基类的Creator使用产品X、Y、Z,继承类可以使用产品X+、Y+、Z,而X+、Y+分别继承于X、Y。
H.7 Factory Method用于生产过程中部分产品或产品部分特性的修改,或生产过程中某个步骤的具体实现的修改(但步骤或流程本身不变)。
I. Builder Mode
I.1 Builder模式主要讲的是如何build,和其他三种模式的关注点不一样。
I.2 将对象的构建和表示分离,使得同样的构建过程可以创建不同的表示。
I.3 以RTF转换器为例子,对同一篇文档,构建过程是一样的(ParseRTF),但使用不同的Converter可以得到不同的表示:相同的构建过程,不同的构建结果。
I.4 所有的Builder必须具有相同的抽象借口,这样不同的Builder之间才能互相替换。
I.5 Director负责下达指令,Builder负责执行指令;一个是领导,一个是员工;Director有图纸,Builder负责造。
I.6 框架提供各种Builder,Client创建Director对象,并用所需的builder配置它。
I.7 Builder Mode隐藏了产品内部结构。
J. Abstract Factory和Builder模式的联合应用
Director是设计院,Builder是施工队,Factory是材料商。Director的七姑的八姨的小舅子是材料商的女儿的同学的妈妈的孩子:
// Director告诉Builder必须使用指定factory提供的材料
void StandardBuilder::BuildRoom(Factory& factory, int n)
{
Room *room = factory.CreateRoom();
......
}
K. Builder模式和Abstract Factory模式
K.1 Abstract Factory着重产品系列整体替换;
K.2 Builder模式着重构建过程。
L. Factory Method模式
L.1 以App/Doc框架为例子, App在用户从菜单中选择New时创建Doc。
App知道Doc何时创建,但不知道哪一种Doc子类被创建。
-> 应用场景:知道某个子类何时创建,但暂时还没有该子类的实现。
App/Doc框架不知道具体子类,因此Doc子类对象的创建只能在App子类而不是App父类里做,因此App父类需要一个virtual的CreateDocument函数并由App子对象在其中new一个Doc子对象(App子类是知道Doc子类的)。
父类(框架)负责过程(CreateDoc,docs.Add,doc->Open),这个过程对不同的App/Doc是一致的;子类负责过程中需要用到子类的那些步骤的具体实现。
跳出App/Doc框架来看,Factory Method的基类定好产品工序,子类根据需要修改/实现某一道或某几道工序。改变产品类就必须创建新的子类。
L.2 迷宫游戏原始版本:
Maze* MazeGame::CreateMaze()
{
Maze *aMaze = new Maze();
Room *r1 = new Room(1);
}
L.3 修改版本:
Maze* MazeGame::CreateMaze()
{
Maze *aMaze = MakeMaze(); // Virtual function
Room *r1 = MakeRoom(1); // Virtual function
}
M. 类平行层次
一个类将它的一些职责委托给一个独立的类。被委托的职责仅在较少的时候需要,因此不需要被保存在委托方。
N. ProtoType模式
----------------------------------- 分割线 ----------------------------------
原文的下面这句话似乎翻译有问题:
……为定义选择板中的那些工具,还提供一个抽象类Tool。该框架还为一些创建图形对象实例并将它们加入到文档中的工具预定义了一个GraphicTool子类……
这句话的意思应该是这样:
……为定义选择板中的那些工具,还提供一个抽象类Tool。例如,该框架为创建图形对象并将它们加入到文档的工具预定义了一个GraphicTool子类。类似的还有旋转图形对象的RotateTool子类。
----------------------------------- 分割线 ----------------------------------
N.1 GraphicTool用来对Graphic进行操纵,这样初想起来Manipulate类似乎可以作为Graphic的一个成员。然而这种想法并不好;理由:
1) Manipulate类不是Graphic类自身所具有的某种“属性”或“方法”,而是外部强加上去的;相比较而言,Draw操作就是Graphic类自身应该具有的。
2) 不同Graphic的同种Manipulate是类似的:比如GraphicTool创建并绘制一个新的Graphic;RotateTool旋转一个Graphic。
3) 施加于Graphic的Manipulate是不可预测的。现在有了GraphicTool和RotateTool,将来可能会有ScaleTool和FlipTool。
以上,一句话概括,解耦了Graphic类和Tool类。
N.2 Graphic需要动态创建,适合于ProtoType。
N.3 为什么需要clone而不是new
因为我们无法预先知道需要创建的对象类型,因此无法new(对于Smalltalk这不是问题;因此这种模式在Smalltalk中用处不大)。用音乐编辑器做例子,用户想要什么样的Graphic,你就要clone什么样的Graphic。框架设计者不可能知道client会鼓捣出什么样的Graphic,于是要求client必须为他们的Graphic提供clone操作供GraphicTool类使用。
O. 几种创建型模式对比
O.1 Abstract Factory
A厂家生产A-Wall,A-Room,A-Door
B厂家生产B-Wall,B-Room,B-Door
C厂家生产C-Wall,C-Room,C-Door
各家产品能够各自配套;建造Maze时可以全用A家的三款产品,也可以全用B家的或全用C家的,但三家产品不能混用。框架设计者已帮你做好三个厂家和他们的所有产品,Client直接用就是了。
不同厂家的同种材料之间是并列关系。
O.2 Factory Method
已经有了基本的砖头Wall、木质Room和拼木的Door,通过继承使用刷了漆的Wall、装修过的Room以及实木的Door。Wall、Room和Door这三款产品可以仅替换其中一个。框架设计者只提供基本的Wall、Room和Door,更精美的Wall、Room和Door由Client通过继承实现。框架设计者还提供了Maze的构建过程。Client一般不修改构建过程,只通过继承Maze并在Derived Maze中使用自己的材料来替换标准材料。
同种材料之间存在继承关系;不存在直接的并列关系。
O.3 PROTOTYPE
有的Client想要A-Wall,B-Door,C-Room;
有的Client想要A-Wall,D-Door,E-Room……
如果是Factory Method,每种组合都需要一个MazeGame子类;使用ProtoType,只要用不同的对象构造MazeFactory。
[本文首发于cnblogs 作者:byeyear Email:[email protected] 转载请注明]