热门问题
时间线
聊天
视角
抽象工廠模式
来自维基百科,自由的百科全书
Remove ads
在軟體工程中,抽象工廠模式(英語:abstract factory pattern),是一種軟體設計模式。抽象工廠模式提供了一種方式,可以將一組具有同一主題的單獨的工廠封裝起來。抽象工廠模式將一組對象的實現細節與他們的一般使用分離開來。

在正常使用中,客戶端程式需要建立抽象工廠的具體實現,然後使用抽象工廠作為介面來建立這一主題的具體對象。客戶端程式不需要知道(或關心)它從這些內部的工廠方法中獲得對象的具體類型,因為客戶端程式僅使用這些對象的通用介面。
概述
工廠是建立產品(對象)的地方,其目的是將產品的建立與產品的使用分離。抽象工廠模式的目的,是將若干抽象產品的介面與不同主題產品的具體實現分離開。這樣就能在增加新的具體工廠的時候,不用修改參照抽象工廠的客戶端代碼。
抽象工廠模式的實質是「提供介面,建立一系列相關或獨立的對象,而不指定這些對象的具體類。」[1]使用抽象工廠模式,能夠在具體工廠變化的時候,不用修改使用工廠的客戶端代碼,甚至是在執行時。然而,使用這種模式或者相似的設計模式,可能給編寫代碼帶來不必要的複雜性和額外的工作。正確使用設計模式能夠抵消這樣的「額外工作」。
舉個例子來說,比如一個抽象工廠類叫做DocumentCreator(文件建立器),此類提供建立若干種產品的介面,包括createLetter()(建立信件)和createResume()(建立簡歷)。其中,createLetter()返回一個Letter(信件),createResume()返回一個Resume(簡歷)。
系統中還有一些DocumentCreator的具體實現類,包括FancyDocumentCreator和ModernDocumentCreator。這兩個類對DocumentCreator的兩個方法分別有不同的實現,用來建立不同的「信件」和「簡歷」(用FancyDocumentCreator的實例可以建立FancyLetter和FancyResume,用ModernDocumentCreator的實例可以建立ModernLetter和ModernResume)。這些具體的「信件」和「簡歷」類均繼承自抽象類,即Letter和Resume類。
客戶端需要建立「信件」或「簡歷」時,先要得到一個合適的DocumentCreator實例,然後呼叫它的方法。一個工廠中建立的每個對象都是同一個主題的(「fancy」或者「modern」)。客戶端程式只需要知道得到的對象是「信件」或者「簡歷」,而不需要知道具體的主題,因此客戶端程式從抽象工廠DocumentCreator中得到了Letter或Resume類的參照,而不是具體類的對象參照。
Remove ads
結構

在上面的UML類圖中,Client類要求ProductA和ProductB對象,但它不直接的實例化ProductA1和ProductB1類。Client轉而提及AbstractFactory介面來建立對象,這使得Client獨立於如何建立對象(哪個具體具體類被實例化)。Factory1類通過實例化ProductA1和ProductB1類來實現AbstractFactory介面。
UML序列圖展示了執行時互動:Client對象呼叫createProductA()於Factory1對象之上,它建立並返回一個ProductA1對象。此後,Client呼叫createProductB()於Factory1之上,它建立並返回一個ProductB1對象。
使用
在以下情況可以考慮使用抽象工廠模式:
- 一個系統要獨立於它的產品的建立、組合和表示時。
- 一個系統要由多個產品系列中的一個來組態時。
- 需要強調一系列相關的產品對象的設計以便進行聯合使用時。
- 提供一個產品類別館,而只想顯示它們的介面而不是實現時。
抽象工廠模式的優點有:具體產品從客戶代碼中被分離出來,容易改變產品的系列,將一個系列的產品族統一到一起建立。其缺點是:在產品族中擴充新的產品是很困難的,它需要修改抽象工廠的介面。
具體的工廠決定了建立對象的具體類型,而且工廠就是對象實際建立的地方(比如在C++中,用「new」運算子建立對象)。然而,抽象工廠只返回一個指向建立的對象的抽象參照(或指標)。這樣,客戶端程式呼叫抽象工廠參照的方法,由具體工廠完成對象建立,然後客戶端程式得到的是抽象產品的參照。如此使客戶端代碼與對象的建立分離開來。[3]
因為工廠僅僅返回一個抽象產品的參照(或指標),所以客戶端程式不知道(也不會牽絆於)工廠建立對象的具體類型。然而,工廠知道具體對象的類型;例如,工廠可能從設定檔中讀取某種類型。這時,客戶端沒有必要指定具體類型,因為已經在設定檔中指定了。通常,這意味著:
- 客戶端代碼不知道任何具體類型,也就沒必要引入任何相關的標頭檔或類別定義。客戶端代碼僅僅處理抽象類型。工廠確實建立了具體類型的對象,但是客戶端代碼僅使用這些對象的抽象介面來訪問它們。[4]
- 如果要增加一個具體類型,只需要修改客戶端代碼使用另一個工廠即可,而且這個修改通常只是一個檔案中的一行代碼。不同的工廠建立不同的具體類型的對象,但是和以前一樣返回一個抽象類型的參照(或指標),因此客戶端代碼的其他部分不需要任何改動。這樣比修改客戶端代碼新增類型的對象簡單多了。如果是後者的話,需要修改代碼中每一個建立這種對象的地方(而且需要注意的是,這些地方都知道對象的具體類型,而且需要引入具體類型的標頭檔或類別定義)。如果所有的工廠對象都儲存在全域的單例對象中,所有的客戶端代碼到這個單例中訪問需要的工廠,那麼,更換工廠就非常簡單了,僅僅需要更改這個單例對象即可。[4]
代碼舉例
假設我們有兩種產品介面Button和Border ,每一種產品都支援多種系列,比如Mac系列和Windows系列。這樣每個系列的產品分別是MacButton、WinButton、MacBorder和WinBorder。為了可以在執行時刻建立一個系列的產品族,我們可以為每個系列的產品族建立一個工廠MacFactory和WinFactory。每個工廠都有兩個方法CreateButton和CreateBorder並返回對應的產品,可以將這兩個方法抽象成一個介面AbstractFactory 。這樣在執行時刻我們可以選擇建立需要的產品系列。
在抽象工廠模式中,通常一個工廠能夠建立若干種不同類型的對象,為了簡潔起見,以上類圖僅僅展示了建立一個類型對象的工廠。GuiFactory介面中的createButton方法返回Button類型的對象。返回Button的哪種實現依賴於使用GuiFactory的哪種實現。
Remove ads
Java的例子:
//定义接口
public interface Button {}
public interface Border {}
//实现抽象类
public class MacButton implements Button {}
public class MacBorder implements Border {}
public class WinButton implements Button {}
public class WinBorder implements Border {}
//实现工厂
public class MacFactory {
public static Button createButton() {
return new MacButton();
}
public static Border createBorder() {
return new MacBorder();
}
}
//客户使用
public class WinFactory {
public static Button createButton() {
return new WinButton();
}
public static Border createBorder() {
return new WinBorder();
}
}
Remove ads
Python的例子:
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def paint(self): pass
class Border(ABC):
@abstractmethod
def paint(self): pass
class MacButton(Button):
def paint(self):
print("It is a MacButton.")
class MacBorder(Border):
def paint(self):
print("It is a MacBorder.")
class WinButton(Button):
def paint(self):
print("It is a WinButton.")
class WinBorder(Border):
def paint(self):
print("It is a WinBorder.")
class GUIFactory(ABC):
@abstractmethod
def create_button(self): pass
@abstractmethod
def create_border(self): pass
class MacFactory(GUIFactory):
def create_button(self):
return MacButton()
def create_border(self):
return MacBorder()
class WinFactory(GUIFactory):
def create_button(self):
return WinButton()
def create_border(self):
return WinBorder()
其執行:
>>> factory1 = WinFactory()
>>> factory1.create_button().paint()
It is a WinButton.
>>> factory1.create_border().paint()
It is a WinBorder.
>>> factory2 = MacFactory()
>>> factory2.create_button().paint()
It is a MacButton.
>>> factory2.create_border().paint()
It is a MacBorder.
Remove ads
下面是基於《設計模式》書中前C++98實現的C++23實現迷路園遊戲例子:
import std;
using std::array;
using std::shared_ptr;
using std::unique_ptr;
using std::vector;
enum class Direction: char {
NORTH,
SOUTH,
EAST,
WEST
};
class MapSite {
public:
virtual void enter() = 0;
virtual ~MapSite() = default;
};
class Room: public MapSite {
private:
int roomNumber;
shared_ptr<array<MapSite, 4>> sides;
public:
explicit Room(int n = 0):
roomNumber{n} {}
~Room() = default;
Room& setSide(Direction d, MapSite* ms) {
sides[static_cast<size_t>(d)] = std::move(ms);
std::println("Room::setSide {} ms", d);
return *this;
}
virtual void enter() override = 0;
Room(const Room&) = delete;
Room& operator=(const Room&) = delete;
};
class Wall: public MapSite {
public:
explicit Wall(int n = 0):
MapSite(n) {}
~Wall() = default;
void enter() override {
// ...
}
};
class Door: public MapSite {
private:
shared_ptr<Room> room1;
shared_ptr<Room> room2;
public:
explicit Door(int n = 0, shared_ptr<Room> r1 = nullptr, shared_ptr<Room> r2 = nullptr):
MapSite(n), room1{std::move(r1)}, room2{std::move(r2)} {}
~Door() = default;
void enter() override {
// ...
}
Door(const Door&) = delete;
Door& operator=(const Door&) = delete;
};
class Maze {
private:
vector<shared_ptr<Room>> rooms;
public:
Maze() = default;
~Maze() = default;
Maze& addRoom(shared_ptr<Room> r) {
std::println("Maze::addRoom {}", reinterpret_cast<void*>(r.get()));
rooms.push_back(std::move(r));
return *this;
}
shared_ptr<Room> roomNo(int n) const {
for (const Room& r: rooms) {
// actual lookup logic here...
}
return nullptr;
}
};
class MazeFactory {
public:
MazeFactory() = default;
virtual ~MazeFactory() = default;
[[nodiscard]]
unique_ptr<Maze> makeMaze() const {
return std::make_unique<Maze>();
}
[[nodiscard]]
shared_ptr<Wall> makeWall() const {
return std::make_shared<Wall>();
}
[[nodiscard]]
shared_ptr<Room> makeRoom(int n) const {
return std::make_shared<Room>(new Room(n));
}
[[nodiscard]]
shared_ptr<Door> makeDoor(shared_ptr<Room> r1, shared_ptr<Room> r2) const {
return std::make_shared<Door>(std::move(r1), std::move(r2));
}
};
// If createMaze is passed an object as a parameter to use to create rooms, walls, and doors, then you can change the classes of rooms, walls, and doors by passing a different parameter. This is an example of the Abstract Factory (99) pattern.
class MazeGame {
public:
Maze() = default;
~Maze() = default;
[[nodiscard]]
unique_ptr<Maze> createMaze(MazeFactory& factory) {
unique_ptr<Maze> maze = factory.makeMaze();
shared_ptr<Room> r1 = factory.makeRoom(1);
shared_ptr<Room> r2 = factory.makeRoom(2);
shared_ptr<Door> door = factory.makeDoor(r1, r2);
maze->addRoom(r1)
.addRoom(r2)
.setSide(Direction::NORTH, factory.makeWall())
.setSide(Direction::EAST, door)
.setSide(Direction::SOUTH, factory.makeWall())
.setSide(Direction::WEST, factory.makeWall())
.setSide(Direction::NORTH, factory.makeWall())
.setSide(Direction::EAST, factory.makeWall())
.setSide(Direction::SOUTH, factory.makeWall())
.setSide(Direction::WEST, door);
return maze;
}
};
int main(int argc, char* argv[]) {
MazeGame game;
unique_ptr<Maze> maze = game.createMaze(MazeFactory());
}
程式的輸出為:
Maze::addRoom 0x1317ed0
Maze::addRoom 0x1317ef0
Room::setSide 0 0x1318340
Room::setSide 2 0x1317f10
Room::setSide 1 0x1318360
Room::setSide 3 0x1318380
Room::setSide 0 0x13183a0
Room::setSide 2 0x13183c0
Room::setSide 1 0x13183e0
Room::setSide 3 0x1317f10
Remove ads
參考文獻
參見
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads
