一、序言
系列博客文章都是研讀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);
好原型模式到此爲止!