面向對象設計——抽象工廠(Abstract Factory)模式
定義
提供一個創建一系列相關或者相互依賴對象的接口,而無需指定它們具體的類。抽象工廠允許客戶使用抽象的接口來創建一組相關的產品,而不需要知道或關心實際產出的具體產品是什麽。這樣一來,客戶就能從具體的產品中被解耦。
適用場景
在以下情況可以使用Abstract Factory模式
- 一個系統要獨立於它的產品的創建、組合和表示時
- 一個系統要由多個產品系列中的一個來配置時
- 當你要強調一系列相關的產品對象的設計以便進行聯合使用時
- 當你提供一個產品類庫,而只想顯示它們的接口而不是實現時
UML圖
抽象工廠模式UML圖a)
- AbstractFactory
——聲明一個創建抽象產品對象的操作接口。
- ConcreteFactory
——實現創建具體產品對象的操作。
- AbstractProduct
——為一類產品對象聲明一個接口。
- ConcreteProduct
——定義一個將被相應的具體工廠創建的產品對象。
——實現AbstractProduct接口。
在這裏指的是ProductA1,ProductA2,ProductB1,ProductB2。
- Client
——僅適用由AbstractFactory和AbstractProduct類聲明的接口。
一般而言,有多少個產品等級結構,就會在工廠角色中發現多少個工廠方法。每一個產品等級結構中有多少個具體的產品,就有多少個產品族,也就會在工廠等級結構中發現多少個具體工廠。
代碼示例
《設計模式:可復用面向對象軟件的基礎》中一書中,給出了一個應用工廠方法設計迷宮的例子,這裏修改為應用抽象工廠模式來實現。
首先,給出了枚舉類型Direction表明東南西北四個方向,定義抽象產品AbstractProduct接口,包含一個純虛函數Enter,用於在迷宮間移動位置。
enum Direction {North, South, East, West}; //AbstractProduct class MapSite{ public: virtual void Enter() = 0; };
接下來,基於抽象產品接口MapSite實現ConcreteProduct
定義迷宮房間Room類(ProductA);
//ProductA class Room : public MapSite{ public: Room(int roomNo);//初始化房間號 MapSite *GetSide(Direction) const;//獲取當前房間四周具體產品ConcreteProduct類MapSite void SetSide(Direction, MapSite*);//設置當前房間四周具體產品ConcreteProduct類MapSite
virtual void Enter();
private:
MapSite* _side[4];
int _roomNumber;
};
定義迷宮的墻Wall類(ProductB);
//ProductB class Wall : public MapSite{ public: Wall(); virtual void Enter(); };
定義迷宮的門Door類(ProductC);
//ProductC class Door : public MapSite{ public: Door(Room * = 0, Room * = 0);//初始化門兩邊的房間 virtual void Enter(); Room* OtherSideFrom(Room *); private: Room * _room1; Room * _room2; bool _isOpen; };
定義迷宮Maze類(ProductD);
//ProductD class Maze{ public: Maze(); void AddRoom(Room *); Room* RoomNo(int) const; private: //... };
在完成抽象產品接口和具體產品類定義後,接下來定義抽象工廠接口AbstractFactory類MazeFactory,對外提供接口,用於生成上述定義的具體產品類ProductA——Room,ProductB——Wall,ProductC——Door,ProductD——Maze。
//AbstractFactory 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);} };
在定義好抽象工廠接口、抽象產品接口、具體產品後,就可以由Client來動態創建和處理各個對象的關系,這裏創建一個包含兩個房間的簡單迷宮。
//Client
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; }
如果需要創建帶各種特效或者屬性的Room、Door、Wall ,只需要繼承具體產品類ProductA、ProductB、ProductC、ProductD,以及抽象工廠AbstractFactory接口MazeFactory。
如果要生成魔法迷宮,只需要繼承MazeFactory生成ConcreteFactory類EnchantedMazeFacotry,
//ConcreteFactory class EnchantedMazeFactory:public MazeFactory{ public: EnchantedMazeFacotry(); 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; };
調用CreateMaze就可以生成帶魔法房間和符咒門的迷宮了。。。
//Client
MazeFactory *enchantedMazeFac = new EnchantedMazeFactory ;
Maze *enchantedMaze = CreateMaze(enchantedMazeFac);
同理還可以,由爆炸工廠生成帶爆炸門和爆炸房間的迷宮。。。
Wall * BombedMazeFactory::MakeWall() const { return new BombedWall; } Room* BombedMazeFactory::MakeRoom(int n) const{ return new RoomWithABomb(n); }
如果嫌上面例子麻煩, 下面這個例子(截圖自wikipedia官網),簡單明了的闡釋了抽象工廠模式:
優缺點
優點:
1、抽象工廠模式隔離了具體類的生產,使得客戶並不需要知道什麽被創建。
2、當一個產品族中的多個對象被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的對象。
3、增加新的具體工廠和產品族很方便,無須修改已有系統,符合“開閉原則”。
缺點:
增加新的產品等級結構很復雜,需要修改抽象工廠和所有的具體工廠類,對“開閉原則”的支持呈現傾斜性。
參考資料:
a) https://en.wikipedia.org/wiki/Abstract_factory_pattern
b) 《設計模式:可復用面向對象軟件的基礎》
c) http://www.runoob.com/design-pattern/abstract-factory-pattern.html
d) https://zh.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82
面向對象設計——抽象工廠(Abstract Factory)模式