一、序言
系列博客文章都是研读Gof的Design Patterns这本书的总结分享,书上的有些例子代码不是很全,这边依葫芦画瓢还原了一些代码,可供运行。目前,网络上很多分享设计模式内容的博客文章,都很经典,其中有个CSDN中的设计模式博客专栏也是研究的Gof的书籍,通俗易懂,让读者对设计模式一目了然。
自己在学习设计模式的过程中,有时候理解一个设计模式挺简单的,但是想要记住它,运用它,往往比较困难,所以系列文章的主要目的就是让设计模式不单单只是以一个软件模式存在于我们的认知中。更多的是想让读者包括我自己在心中织起一张设计模式的网络,更加体系化,在以后的面向对象的编程中,能够熟练地运用,在设计架构时能够信手拈来,多方面评估不同设计模式的优异性。一些基本的设计模式的定义及何时使用恰当这边不多赘述,这是CSDN博客专栏的网址,大家可以前往学习:设计模式博客专栏。
另外系列博客文章以C++语言基础,以一个小的项目讲述同一类型的设计模式,学习的步骤基本是一步一步了解一个小项目时如何构成的,学习的过程中,设计模式的代入感并不会很强,当整个代码浏览完毕,设计模式才会体现出来,摆脱一种带着“就是这种设计模式”的套路去学习设计模式。 Java 的话提供了一些API,我认为少了一点豁然开朗的感觉,用纯C++标准库开发,让人兴奋。 以上只是鄙人愚见,欢迎各路大神赐教。
二、Prototype patterns
2.1
原型模式:首先它是一个创建型模式,所谓的创建型也就是对象实例的创建。原型模式的核心在于他可以对创建出来的对象进行一般化的克隆复制。复制出来的对象,与原对象一般无二,称作“原型”,这个原型是可以定制的。
2.2
原型模式引入的必要性:一个对象的创建,所属类的构造函数和类中的其他成员变量都会进行初始化,而且创建对象的过程new操作,需要底层繁琐的操作,而引入原型模式,在复用对象的时候就可以减少类的初始化和new的使用次数,大大避免资源消耗。克隆一个对象并不会默认的去执行他所属的类构造函数,当然他有专门的拷贝构造函数,这个需要我们自己定义。
2.3
简单的原型模式案例:
上图是Gof书中的原图,其中整个模式中关键类就一个Prototype,下面简单用代码说明下关系:
class Prototype
{
public:
Prototype(){}
Prototype(Prototype& self){_mText = self._mText;}
virtual ~Prototype(){}
virtual Prototype* Clone(){return new Prototype(*this);}
void show(){ cout<<this->_mText<<endl;}
void set(int text){this->_mText = text;}
private:
int _mText;
};
Client是一个类似让一个原型克隆自身从而创建一个新的对象,在具体编程中有可能是个类,有可能是个main函数,这里以最简单的main函数为例:
int main()
{
Prototype *proto = new Prototype();
proto->set(1);
Prototype *proto_copy = proto->Clone();
proto_copy->show();
proto_copy->set(2);
proto_copy->show();
proto->show();
return 0;
}
将以上代码运行,不难看出结果如下:
1 //当拷贝之后,打印原型对象中的_mText值,没有变化为1;
2 //对原型进行定制,重新设置_mText值为2;
1 //再打印被克隆的对象中的_mText值,没有变化,对原型对象的操作不会破坏原对象的数据。
以上就是简单的原始模型结构,这里只涉及到了浅拷贝,C++中的缺省拷贝构造函数实现按成员拷贝,意味着在拷贝对象和原来的对象之间是共享指针的。但克隆一个复杂的对象有事需要深拷贝,因为复制对象与原对象必须保持相互独立,这才是原型模式的意义。
三、Prototype深入理解
一言不合,就贴代码,继续探讨迷宫游戏的话题。以下代码都是基础迷宫构件类,上篇设计模式文章已有接触,这次稍作修改增加了clone方法和拷贝构造函数:
class Room : public MapSiteBase
{
public:
Room(int roomNo){}
Room(const Room& other){_roomNo = other._roomNo;}
~Room(){}
virtual void Enter(){}
virtual Room* Clone() const
{return new Room(*this);}
private:
int _roomNo;
};
class Door : public MapSiteBase
{
public:
Door(Room* = 0,Room* = 0){}
Door(const Door& other){_room1 = other._room1;_room2 = other._room2;}
virtual void Enter(){}
virtual Door* Clone() const
{return new Door(*this);}
void Initialize(Room* r1,Room* r2){_room1 = r1;_room2 = r2;}
private:
Room* _room1;
Room* _room2;
};
class Wall : public MapSiteBase
{
public:
Wall(){}
Wall(const Wall& other){}
virtual void Enter(){}
virtual Wall* Clone() const
{return new Wall(*this);}
};
class Maze
{
public:
Maze(){}
Maze(const Maze& other){}
void addRoom(Room*){}
Room* RoomNo(int) const{}
virtual Maze* Clone() const
{return new Maze(*this);}
};
以上对每个类都做了修改增加了Clone 方法:
virtual Room* Clone() const
{return new Room(*this);}
这里的对象拷贝还只涉及了浅拷贝,即前面所讲的这两个对象之间的属性还是共享的,当然这是克隆的第一步,原型模式最重要的工作就是深拷贝的编写。
拷贝构造函数因类而已,没有参数的类构造函数相对比较简单:
Wall(const Wall& other){}
这里函数内没做任何事,因为Wall类本身没有什么属性,简单浅拷贝即可满足要求。
稍微复杂的类:
<span style="font-size:18px;">Door(const Door& other){_room1 = other._room1;_room2 = other._room2;}</span>
函数体实现对象属性的拷贝,可以理解为简单的深拷贝。
以上这些类都可以称为原型类,类似Prototype,把这些类改成原型类有什么意义了?我们可以试想一个场景,这是一个迷宫游戏,必然有很多的Room、Wall、Door等组成,按照先前的想法就是创建一个Room就new一个新的对象,Wall、Door皆如此,这显然不是最好的方案,Room之间没有太大的差异,完全可以先new出一个Room,其他的Room以此为模板进行克隆,当然在此基础上你也可以对Room进行定制,这将大大减少系统创建对象的复杂程度。
以下是一个继承MazeFactory的prototype工厂类:
class MazePrototypeFactory : public MazeFactory
{
public:
//构造函数,初始化它的‘原型’,这里指模板
MazePrototypeFactory(Maze* m,Wall* w,Room* r,Door* d){
_prototypeMaze = m;
_prototypeRoom = r;
_prototypeWall = w;
_prototypeDoor = d;
}
virtual ~MazePrototypeFactory(){}
//重载make方法,返回克隆的原型,这里涉及浅拷贝
virtual Maze* makeMaze() const{return _prototypeMaze->Clone();}
virtual Wall* makeWall() const{return _prototypeWall->Clone();}
//重载make方法,对其克隆的原型中的一些属性进行拷贝,这里涉及深拷贝
virtual Room* makeRoom(int num) const{
Room *room = _prototypeRoom->Clone();
room->_roomNo = num;//属性拷贝,当然Room中的拷贝构造函数也有拷贝动作,这里的
//的行为可以认为客户初始化克隆对象的房间。
return room;
}
virtual Door* makeDoor(Room* r1,Room* r2) const{
Door* door = _prototypeDoor->Clone();
door->Initialize(r1,r2);//also as follow
//_room1 = r1;
//_room2 = r2;
return door;
}
private:
Maze* _prototypeMaze;
Room* _prototypeRoom;
Wall* _prototypeWall;
Door* _prototypeDoor;
};
Maze *maze;
MazeGame mgame;
MazePrototypeFactory simpleMazeFactory(new Maze,new Wall,new Room,new Door);
maze = mgame.createMaze(simpleMazeFactory);
以下是creatMaze的简单源码:
<span style="font-size:18px;">Maze* MazeGame::createMaze(MazeFactory &factory)
{
Maze *maze = factory.makeMaze();
Room *r1 = factory.makeRoom(1);
Room *r2 = factory.makeRoom(2);
/*构造无数个Room的时候,原型模式的优势就出来了*/
Door *door = factory.makeDoor(r1,r2);
maze->addRoom(r1);
maze->addRoom(r2);
/*door init*/
}</span>
到这里一个原型模式的优势已经可以体现出来了。为了改变迷宫的类型,我们完全可以用一个不同的迷宫集合来初始化MazePrototypeFactory,先定义一个会爆炸的墙:
class BombedWall : public Wall
{
public:
BombedWall(){}
BombedWall(const BombedWall& other):Wall(other){_bomb = other._bomb;}
virtual void Enter(){}
virtual Wall* Clone() const
{return new BombedWall(*this);}
private:
bool _bomb;
};
有炸弹的Room:
<span style="font-size:18px;">class RoomWithABomb
{
//同上;
}</span>
这么一来看一下怎么构建迷宫:
Maze *maze;
MazeGame mgame;
MazePrototypeFactory simpleMazeFactory(new Maze,new BomedWall,new RoomWithABomb,new Door);
maze = mgame.createMaze(simpleMazeFactory);
好原型模式到此为止!