Design Pattern----Creational Patterns

设想有这么一个问题,在一个迷宫游戏中,首先需要的是迷宫地形结构(地图),迷宫地图中包含了若干个格子,每个格子代表一个房间,然后每个房间四周可以有墙、有门等,现在要求用程序创建这么一个地图。

为了更好地理解迷宫的地图,看一张迷宫结构的关系图:


Maze代表迷宫类,里面里面有一些方法,比如AddRoom()方法,为迷宫添加房间;比如RoomNo()方法,根据房间号去查找对应的房间等等。

MapSite是迷宫组成元素的一个基类,下面有三个继承类分别是:Room(房间)、Wall(墙)、Door(门)。

要构建这么一个Maze地图,最简单粗暴的方法就是地图创建的需要,直接用硬编码的方式(比如用new 加类名创建一个对象)将每个Maze组成元素创建出来,并且添加到Maze当中去。

具体实现如下:

</pre><pre>

Maze* MazeGame::CreateMaze ()
{
	Maze* aMaze = new Maze;
	Room* r1 = new Room(1);
	Room* r2 = new Room(2);
	Door* theDoor = new Door(r1, r2);
	aMaze->AddRoom(r1);
	aMaze->AddRoom(r2);
	r1->SetSide(North, new Wall);
	r1->SetSide(East, theDoor);
	r1->SetSide(South, new Wall);
	r1->SetSide(West, new Wall);
	r2->SetSide(North, new Wall);
	r2->SetSide(East, new Wall);
	r2->SetSide(South, new Wall);
	r2->SetSide(West, theDoor);
	return aMaze;
}


这样虽然能够实现这个功能,但是玩过迷宫类似的游戏就知道,有些不同房间之间的风格是不一样的(比如背景、比如一些门可以被炸开等等),这就意味这需要需要用不同的类来表不同风格的同一个Maze元素,比如可能有Room0, Room1, Room2分别从积累Room继承而来,代表三种不同风格的房间。这样一来,如果要改变Maze的风格,那所有新建Maze元素的地方都需要跟着改变,相当于牵一发而动全身了,代码的灵活性不够好,而且还在改的过程中极其麻烦还容易出错。

在这种背景下,Creational Patterns(创建模式)就有勇武之地了,它是一种将类的实例化过程抽象出来的一种方法。创建模式包括5种具体的模式:Abstract Factory(抽象工厂模式), Builder(创建者模式,生成器模式), Factory Method(工厂方法模式), Prototype(原型模式), Singleton(单例模式)。

Abstract Factory:

Abstract Factory提供了一个抽象的接口,通过这个接口可以创建具有相同风格的一类对象,在创建的过程中不需要知道正在创建的具体是哪一种风格。这样的话,无论具体风格类怎么改,不管具体风格类是什么名字,在实例化的过程中,都只需要用Abstract Factory提供的统一的抽象接口去实现。

Abstract Factory的具体结构如下:

Abstract Factory作为父类,提供一个抽象的接口供Client调用实例化,ConcreteFactory1和ConcreateFactory2都继承于AbstractFactory,代表两个具体的风格类,这些具体的风格类中实现了各自风格的Product。具体来说,Client有两类抽象的类指针,分别是AbstractFactory(Factory指针)和AbstractProduct(Product指针,每个Product对应一个这样的抽象指针),Client调用AbstractFactory中的CreateProduct,CreateProduct生成具体风格的Product并返回,然后Client就用AbstractProduct指向返回的这个生成并返回的Product,这样就完成了了Product的创建。具体到之前讲的Maze地图,Product就可以代表Maze的组成元素,比如Room、Wall、Door。

那么通过Abstract Factory模式,就可以这样来创建Maze地图:

class MazeFactory
{
public:
	MazeFactory();
	virtual Maze* MakeMaze() const
	{ return new Maze; }
	virtual Wall* MakeWall() const
	{ return new Wall; }
	virtual Room* MakeRoom(int n) const
	{ return new Room(n); }
	virtual Door* MakeDoor(Room* r1, Room* r2) const
	{ return new Door(r1, r2); }
};
class EnchantedMazeFactory : public MazeFactory
{
public:
	EnchantedMazeFactory();
	virtual Room* MakeRoom(int n) const
	{ return new EnchantedRoom(n, CastSpell()); }
	virtual Door* MakeDoor(Room* r1, Room* r2) const
	{ return new DoorNeedingSpell(r1, r2); }
protected:
	Spell* CastSpell() const;
};<pre name="code" class="cpp">Maze* MazeGame::CreateMaze (MazeFactory& factory)
{
	Maze* aMaze = factory.MakeMaze();
	Room* r1 = factory.MakeRoom(1);
	Room* r2 = factory.MakeRoom(2);
	Door* aDoor = factory.MakeDoor(r1, r2);
	aMaze->AddRoom(r1);
	aMaze->AddRoom(r2);
	r1->SetSide(North, factory.MakeWall());
	r1->SetSide(East, aDoor);
	r1->SetSide(South, factory.MakeWall());
	r1->SetSide(West, factory.MakeWall());
	r2->SetSide(North, factory.MakeWall());
	r2->SetSide(East, factory.MakeWall());
	r2->SetSide(South, factory.MakeWall());
	r2->SetSide(West, aDoor);
	return aMaze;
}
 
MazeGame game;
BombedMazeFactory factory;
game.CreateMaze(factory);
BombedMazeFactory是继承于MazeFactory的一个具体类,可以代表一类具体风格的Maze。将BombedMazeFactory一个实例对象传入CreateMaze中,CreateMaze通过一个抽象的MazeFactory引用去生成各种Maze的组成元素,这样一来就把Maze组成元素的具体创建过程封装了,而且在创建的时候也不知道具体创建了哪一类Maze。


Builder:

书上是这样解释的,通过Builder(生成器模式)这种模式,可以将复杂对象的创建和显示分离,这样同一个创建过程可以创建不同的显示。感觉可以更通俗点理解:首先确定要创建什么,确定之后每次把要创建的信息发送给Builder,Builder自动帮你创建好并添加到创建结果,当所有创建过程完成之后, 就可以通过Builder对象去访问最终创建结果。而具体怎么创建,怎么把新创建的信息添加到现有结果中,这些过程都不需要关心。

Builder的具体结构如下:


上图中,Director首先确定要创建什么,确定要创建什么之后,对与每个需要创建的信息,都通过一个抽象的Builder指针调用一个BuilderPart()函数去创建,这个时候,Director方面就不需要管Builder具体是怎么创建并把创建结果添加到现有的创建结果中。当然对于不同的创建风格,还可以有不同的Builder类,比如上图中,Builder只是一个基类,可以通过继承的方式创建出它的多个具体的子类,在子类中可以根据自己不同的风格重新实现具体的创建、组成方法。当整个过程创建完之后,就可以通过抽象的Builder指针去得到一个完整的Product。或许,在某些情况下,在Product创建完成之后,需要知道中间某些过程中的创建情况,这种情况的话, 就需要用一个特殊的数据结构来维护Product,比如可以简单地用一棵树来保存Product各个创建阶段的具体创建情况,不同节点代表不同创建阶段的创建情况。具体到之前说的迷宫例子,这里的Product就代表Maze地图。

那么通过Builder模式,可以这样来创建Maze地图:

class MazeBuilder
{
public:
	virtual void BuildMaze() { }
	virtual void BuildRoom(int room) { }
	virtual void BuildDoor(int roomFrom, int roomTo) { }
	virtual Maze* GetMaze() { return 0; }
protected:
	MazeBuilder();
};
class StandardMazeBuilder : public MazeBuilder
{
public:
	StandardMazeBuilder();
	virtual void BuildMaze();
	virtual void BuildRoom(int);
	virtual void BuildDoor(int, int);
	virtual Maze* GetMaze();
private:
	Direction CommonWall(Room*, Room*);
	Maze* _currentMaze;
};
Maze* MazeGame::CreateMaze (MazeBuilder& builder)
{
	builder.BuildMaze();
	builder.BuildRoom(1);
	builder.BuildRoom(2);
	builder.BuildDoor(1, 2);
	return builder.GetMaze();
}
Maze* maze;
MazeGame game;
StandardMazeBuilder builder;
game.CreateMaze(builder);
maze = builder.GetMaze();
StandardMazeBuilder是MazeBuilder的一个子类,代表一类创建风格,在这个子类里面,根据类本身的特点重新实现了各种Build方法。首先,将MazeBuillder的一个具体的子类StandardMazeBuilder的一个对象传入CreateMaze中,在CreateMaze中就可以通过一个抽象的MazeBuilder引用,调用各种创建方法去创建Maze中的组成元素,创建完成以后,可以通过抽象的MazeBuilder引用去获得最终创建结果。


Factory Method:

Factory Method(工厂方法模式)定义了一个抽象的接口用于创建对象,但是让具体的类去决定对哪一个类进行实例化,实例化后并返回结果。运用Factory Method,首先建立一个基类,提供了抽象的创建Maze组成元素的接口,之后可以派生出具体不同的子类,每个子类对Maze中的抽象接口根据类本身的特点重新实现。

Factory Method的具体结构如下:

上图中,Creator作为一个基类,提供了一个抽象的方法FactoryMethod()用于创建Product,在Creator的下面可以派生出各种不同的子类,各个基类可以根据类本身特征重新实现一些抽象接口。具体到我们之前讲的例子,这里的Product可以是Maze里面的组成元素。

那么通过Factory Method模式,可以这样来创建Maze地图:

</pre><pre>
class BombedMazeGame : public MazeGame
{
public:
	BombedMazeGame();
	virtual Wall* MakeWall() const
	{ return new BombedWall; }
	virtual Room* MakeRoom(int n) const
	{ return new RoomWithABomb(n); }
};
Maze* MazeGame::CreateMaze ()
{
	Maze* aMaze = MakeMaze();
	Room* r1 = MakeRoom(1);
	Room* r2 = MakeRoom(2);
	Door* theDoor = MakeDoor(r1, r2);
	aMaze->AddRoom(r1);
	aMaze->AddRoom(r2);
	r1->SetSide(North, MakeWall());
	r1->SetSide(East, theDoor);
	r1->SetSide(South, MakeWall());
	r1->SetSide(West, MakeWall());
	r2->SetSide(North, MakeWall());
	r2->SetSide(East, MakeWall());
	r2->SetSide(South, MakeWall());
	r2->SetSide(West, theDoor);
	return aMaze;
}
MazeGame作为一个抽象的Creator, 提供MakeRoom(), MakeDoor(), MakeWall()的抽象接口作为Factory Method。BombedMazeGame继承于MazeGame,对Factory Method具体实现了。这里容易和Abstract Factory模式混淆,在Abstract Factory模式中,Make方法单独地在一个Factory类里面,而在Factory Method模式中,Make方法直接放到Game类里面去了。

Prototype:

运用Prototype(原型模式)这个模式进行实例化时,不是我们之前讲的通过新建一个对象的方式,而是直接复制原型实例进行实现。

Prototype的具体结构如下:


上图中,Prototype作为一个基类,提供一个抽象的Clone()接口,用于Copy实例对象。下面派生出来的两个具体的Prototype根据各自特点重新实现Clone()接口。Prototype模式中,最重要的就是Clone()接口的实现,拷贝的时候要注意是用深拷贝还是浅拷贝,有的时候还需要考虑循环引用的问题。具体到之前说的Maze地图例子,可以为每一个Maze组成元素提供一个抽象的Prototype类,然后根据Maze的不同风格去派生出不同的具体子类。

那么通过Prototype模式,可以这样来创建Maze地图:

class MazePrototypeFactory : public MazeFactory
{
public:
	MazePrototypeFactory(Maze*, Wall*, Room*, Door*);
	virtual Maze* MakeMaze() const;
	virtual Room* MakeRoom(int) const;
	virtual Wall* MakeWall() const;
	virtual Door* MakeDoor(Room*, Room*) const;
private:
	Maze* _prototypeMaze;
	Room* _prototypeRoom;
	Wall* _prototypeWall;
	Door* _prototypeDoor;
};
MazeGame game;
MazePrototypeFactory simpleMazeFactory(
new Maze, new Wall, new Room, new Door
);
Maze* maze = game.CreateMaze(simpleMazeFactory);
在MazePrototypeFactory类中,关键在于四个prototype指针,对应的四个Make函数通过对应的prototype指针去Clone实例,比如MakeRoom():

Room* MazePrototypeFactory::MakeRoom () const
{
	return _prototypeRoom->Clone();
}
Clone是Prototype模式的最关键部分,它是在Maze组成元素类当中实现的,比如Wall的Clone函数的实现:

Door* Door::Clone () const
{
	return new Door(*this);
}
这样实现Clone函数,还要依赖于被Clone的类的拷贝构造函数,也就是说每个被Clone的类需要实现自身的拷贝构造函数。


Singleton:
Singleton(单例模式)模式确保每个类只有一个实例,并提供一个全局访问的指针。

Singleton模式的具体结构如下:


在Singleton模式中,两个关键部分在于一个指向唯一实例对象的指针,一个返回唯一实例对象的函数。具体应用中,需要将Singleton的构造函数放到Protected中,以防被外部意外创建实例,从而就破坏了Singleton只有一个实例的性质。

具体到之前讲的Maze地图的建造,可以实现如下:

class MazeGame
{
public:
	Maze* CreateMaze();
	// factory methods:</span>
	virtual Maze* MakeMaze() const
	{ return new Maze; }
	virtual Room* MakeRoom(int n) const
	{ return new Room(n); }
	virtual Wall* MakeWall() const
	{ return new Wall; }
	virtual Door* MakeDoor(Room* r1, Room* r2) const
	{ return new Door(r1, r2); }
};


 对于Singleton模式的实现,也有一些具体的技巧,比如通过注册Singleton,在以后创建实例的时候可以直接根据类的的代号(可以是名字,也可以是编号)来调用具体的类来创建实例。 


五种Creational模式各有优劣势,在具体应用中具体情况具体分析选取合适的模式很重要。


参考文献:

[1] Erich GammaRichard Helm,Ralph Johnson,John Vlissides. Design Patterns: Elements of Reusable Object Oriented Software.Pearson Education.



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值