热门问题
时间线
聊天
视角

抽象工廠模式

来自维基百科,自由的百科全书

抽象工厂模式
Remove ads

軟體工程中,抽象工廠模式(英語:abstract factory pattern),是一種軟體設計模式。抽象工廠模式提供了一種方式,可以將一組具有同一主題的單獨的工廠封裝起來。抽象工廠模式將一組對象的實現細節與他們的一般使用分離開來。

Thumb
統一塑模語言中的類別圖來表示抽象工廠

在正常使用中,客戶端程式需要建立抽象工廠的具體實現,然後使用抽象工廠作為介面來建立這一主題的具體對象。客戶端程式不需要知道(或關心)它從這些內部的工廠方法中獲得對象的具體類型,因為客戶端程式僅使用這些對象的通用介面。

概述

工廠是建立產品(對象)的地方,其目的是將產品的建立與產品的使用分離。抽象工廠模式的目的,是將若干抽象產品的介面與不同主題產品的具體實現分離開。這樣就能在增加新的具體工廠的時候,不用修改參照抽象工廠的客戶端代碼。

抽象工廠模式的實質是「提供介面,建立一系列相關或獨立的對象,而不指定這些對象的具體類。」[1]使用抽象工廠模式,能夠在具體工廠變化的時候,不用修改使用工廠的客戶端代碼,甚至是在執行時。然而,使用這種模式或者相似的設計模式,可能給編寫代碼帶來不必要的複雜性和額外的工作。正確使用設計模式能夠抵消這樣的「額外工作」。

舉個例子來說,比如一個抽象工廠類叫做DocumentCreator(文件建立器),此類提供建立若干種產品的介面,包括createLetter()(建立信件)和createResume()(建立簡歷)。其中,createLetter()返回一個Letter(信件),createResume()返回一個Resume(簡歷)。

系統中還有一些DocumentCreator的具體實現類,包括FancyDocumentCreatorModernDocumentCreator。這兩個類對DocumentCreator的兩個方法分別有不同的實現,用來建立不同的「信件」和「簡歷」(用FancyDocumentCreator的實例可以建立FancyLetterFancyResume,用ModernDocumentCreator的實例可以建立ModernLetterModernResume)。這些具體的「信件」和「簡歷」類均繼承自抽象類,即LetterResume類。

客戶端需要建立「信件」或「簡歷」時,先要得到一個合適的DocumentCreator實例,然後呼叫它的方法。一個工廠中建立的每個對象都是同一個主題的(「fancy」或者「modern」)。客戶端程式只需要知道得到的對象是「信件」或者「簡歷」,而不需要知道具體的主題,因此客戶端程式從抽象工廠DocumentCreator中得到了LetterResume類的參照,而不是具體類的對象參照。

Remove ads

結構

Thumb
抽象工廠模式的樣例UML類圖和序列圖[2]

在上面的UML類圖中,Client類要求ProductAProductB對象,但它不直接的實例化ProductA1ProductB1類。Client轉而提及AbstractFactory介面來建立對象,這使得Client獨立於如何建立對象(哪個具體具體類被實例化)。Factory1類通過實例化ProductA1ProductB1類來實現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 。這樣在執行時刻我們可以選擇建立需要的產品系列。

Thumb

在抽象工廠模式中,通常一個工廠能夠建立若干種不同類型的對象,為了簡潔起見,以上類圖僅僅展示了建立一個類型對象的工廠。GuiFactory介面中的createButton方法返回Button類型的對象。返回Button的哪種實現依賴於使用GuiFactory的哪種實現。

Remove ads

Java

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

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++

下面是基於《設計模式》書中前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

參考文獻

參見

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads