热门问题
时间线
聊天
视角

工廠方法模式

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

工厂方法模式
Remove ads

工廠方法模式(英語:factory method pattern),是一種實現了工廠概念的物件導向設計模式。就像其他建立型模式一樣,它也是處理在不指定對象具體類型的情況下建立對象的問題。

Thumb
UML描述的工廠方法模式

概述

對象建立中的有些過程包括決定建立哪個對象、管理對象的生命周期,以及管理特定對象的建立和銷毀的概念。工廠方法模式的實質是「定義一個建立對象的介面,但讓實現這個介面的類來決定實例化哪個類。工廠方法讓類別的實例化推遲到子類中進行。」[1]

建立一個對象常常需要複雜的過程,所以不適合包含在一個複合對象中。建立對象可能會導致大量的重複代碼,可能會需要複合對象訪問不到的資訊,也可能提供不了足夠級別的抽象,還可能並不是複合對象概念的一部分。工廠方法模式通過定義一個單獨的建立對象的方法來解決這些問題。由子類實現這個方法來建立具體類型的對象。

結構

Thumb
工廠方法模式的樣例UML類圖[2]

在上面的UML類圖中,Creator類要求Product對象,但不直接的實例化Product1類。Creator轉而提及一個獨立的factoryMethod()來建立一個產品對象,這使得Creator獨立於被實例化的確切的具體類。Creator的子類可以精製(redefine)出哪個類要被實例化。在這個例子中,Creator1子類通過實例化Product1類來實現抽象的factoryMethod()

代碼舉例

Java

下面的迷路園遊戲Java例子,類似於《設計模式》中的例子,這裡針對迷路園生成演算法英語Maze generation algorithm而簡化了對房間的四面、門和牆的處置,即一個房間與毗鄰的另一個房間,如果二者連通則其間有開著的一個門,如果二者不連通則其間有一個牆;永遠開著的門通常就表現為房間的這一面沒有牆,如果要將門具象表現出來則附含著其所處在的牆。

Thumb

抽象類MazeGame連通多個Room對象,但是將建立Room對象的責任委託給了作為具體類的它的子類。MazeGame()構造器是增加一些公用邏輯的模板方法,其中涉及了作為虛方法makeRoom()工廠方法,它在MazeGame的子類中被覆寫,從而可以使用上各種不同的房間:

public abstract class Room {
    abstract void connect(Room room);
}

public class MagicRoom extends Room {
    public void connect(Room room) {}
}

public class OrdinaryRoom extends Room {
    public void connect(Room room) {}
}

public abstract class MazeGame {
    private final List<Room> rooms = new ArrayList<>();
    abstract protected Room makeRoom();
    public MazeGame() {
        Room room1 = makeRoom();
        Room room2 = makeRoom();
        room1.connect(room2);
        rooms.add(room1);
        rooms.add(room2);
    }
}

public class MagicMazeGame extends MazeGame {
    @Override
    protected MagicRoom makeRoom() {
        return new MagicRoom();
    }
}

public class OrdinaryMazeGame extends MazeGame {
    @Override
    protected OrdinaryRoom makeRoom() {
        return new OrdinaryRoom();
    }
}

MazeGame ordinaryGame = new OrdinaryMazeGame();
MazeGame magicGame = new MagicMazeGame();
Remove ads

Python

Python迷路園遊戲例子:

from abc import ABC, abstractmethod

class Room(ABC):
    descr = None
    params = {}
    def __init__(self, **kwargs):
        self.connected_rooms = []
        params = type(self).params | kwargs
        for key, value in params.items():
            self.__dict__[key] = value
    def __str__(self): 
        return type(self).descr
    @abstractmethod
    def connect(self, other, **kwargs): 
        self.connected_rooms.append(other)

class OrdinaryRoom(Room):
    descr = 'Ordinary room'
    params = {'kw1': ''}
    def connect(self, other):
        super().connect(other, kw0='')

class MagicRoom(Room):
    descr = 'Magic room'
    params = {'kw1': ''}
    def connect(self, other):
        super().connect(other, kw0='')

class MazeCreatorTrait(ABC):
    def __init__(self):
        self.rooms = []
        room1 = self.room_factory(kw2='')
        room2 = self.room_factory(kw2='')
        room1.connect(room2)
        self.rooms.append(room1)
        self.rooms.append(room2)
    @abstractmethod
    def room_factory(self): pass
    def play(self):
        print(f"Playing using {self.rooms[0]}")

class MazeCreator(MazeCreatorTrait):
    def __init__(self, creator_cls=OrdinaryRoom):
        self.creator_cls = creator_cls
        super().__init__()
    def room_factory(self, **kwargs):
        return self.creator_cls(**kwargs)

其執行:

>>> maze1 = MazeCreator()
>>> maze1.play()
Playing using Ordinary room
>>> maze2 = MazeCreator(MagicRoom)
>>> maze2.play()
Playing using Magic room
Remove ads

C++

設計模式》書中早於C++98的例子的C++23實現[3]

import std;

enum class ProductId {MINE, YOURS};

// defines the interface of objects the factory method creates.
class Product {
public:
    virtual void print() = 0;
    virtual ~Product() = default;
};

// implements the Product interface.
class ConcreteProductMINE: public Product {
public:
    void print() {
        std::println("this={} print MINE", this);
    }
};

// implements the Product interface.
class ConcreteProductYOURS: public Product {
public:
    void print() {
        std::println("this={} print YOURS", this);
    }
};

// declares the factory method, which returns an object of type Product.
class Creator {
public:
    virtual std::unique_ptr<Product> create(ProductId id) {
        if (ProductId::MINE == id) return std::make_unique<ConcreteProductMINE>();
        if (ProductId::YOURS == id) return std::make_unique<ConcreteProductYOURS>();
        // repeat for remaining products...

        return nullptr;
    }
    virtual ~Creator() = default;
};

int main() {
    // The unique_ptr prevent memory leaks.
    std::unique_ptr<Creator> creator = std::make_unique<Creator>();
    std::unique_ptr<Product> product = creator->create(ProductId::MINE);
    product->print();

    product = creator->create(ProductId::YOURS);
    product->print();
}

程式輸出:

this=0x6e5e90 print MINE
this=0x6e62c0 print YOURS
Remove ads

C#

下面是C#例子:

public interface IProduct {
    string GetName();
    bool SetPrice(double price);
}

public class Phone : IProduct {
    private double _price;

    public string GetName() {
        return "Apple TouchPad";
    }

    public bool SetPrice(double price) {
        _price = price;
        return true;
    }
}

/* Almost same as Factory, just an additional exposure to do something with the created method */
public abstract class ProductAbstractFactory {
    protected abstract IProduct MakeProduct();

    public IProduct GetObject() // Implementation of Factory Method. {
        return this.MakeProduct();
    }
}

public class PhoneConcreteFactory : ProductAbstractFactory {
    protected override IProduct MakeProduct() {
        IProduct product = new Phone();
        // Do something with the object after receiving it
        product.SetPrice(20.30);
        return product;
    }
}
Remove ads

其他用例

  • ADO.NET中,IDbCommand.CreateParameter[4]使用工廠方法連接平行的類階層。
  • Qt中,QMainWindow::createPopupMenu[5]是在框架中定義的工廠方法,可以在應用代碼中重寫。
  • Java中,javax.xml.parsers[6]包使用了幾個工廠方法。例如javax.xml.parsers.DocumentBuilderFactoryjavax.xml.parsers.SAXParserFactory
  • HTML5 DOM API中,文件介面包含createElement()工廠方法用來建立HTMLElement介面的特定元素。

適用性

工廠方法模式,適用於基於介面編程與實現依賴反轉原則。 下列情況可以考慮使用工廠方法模式:

  • 建立對象需要大量重複的代碼。可以把這些代碼寫在工廠基礎類別中。
  • 建立對象需要訪問某些資訊,而這些資訊不應該包含在複合類中。
  • 建立對象的生命周期必須集中管理,以保證在整個程式中具有一致的行為。 對象建立時會有很多參數來決定如何建立出這個對象。
  • 建立對象可能是一個pool里的,不是每次都憑空建立一個新的。而pool的大小等參數可以用另外的邏輯去控制。比如連接池對象,執行緒池對象
  • 業務對象的代碼作者希望隱藏對象的真實類型,而建構函式一定要真實的類名才能用
  • 簡化一些常規的建立過程。根據組態去建立一個對象也很複雜;但可能95%的情況只建立某個特定類型的對象。這時可以弄個函式直接省略那些組態過程。如Java的執行緒池的相關建立api(如Executors.newFixedThreadPool等)
  • 建立一個對象有複雜的依賴關係,比如Foo對象的建立依賴A,A又依賴B,B又依賴C……。於是建立過程是一組對象的的建立和注入。
  • 知道怎麼建立一個對象,但是無法把控建立的時機。需要把「如何建立」的代碼塞給「負責決定什麼時候建立」的代碼。後者在適當的時機,回呼執行建立對象的函式。在支援用函式作為一等公民傳參的語言,比如js,go等,直接用建立函式就行了。對於java需要搞個XXXXFactory的類去傳。
  • 建構函式里不要丟擲異常

工廠方法模式常見於工具包和框架中,在這些庫中可能需要建立客戶端代碼實現的具體類型的對象。

平行的類階層中,常常需要一個階層中的對象能夠根據需要建立另一個階層中的對象。

工廠方法模式可以用於測試驅動開發,從而允許將類放在測試中[7]。舉例來說,Foo這個類建立了一個Dangerous對象,但是Dangerous對象不能放在自動的單元測試中(可能它需要訪問產品資料庫,而這個資料庫不是隨時能夠訪問到的)。所以,就可以把Dangerous對象的建立交由Foo類的一個方法(虛擬函式createDangerous完成。為了測試,再建立一個Foo的一個子類TestFoo,重寫createDangerous方法,在方法中建立並返回一個FakeDangerousDangerous的子類),而這是一個類比對象。這樣,單元測試就可以使用TestFoo來測試Foo的功能,從而避免了使用Dangerous對象帶來的副作用。

參照

參考文獻

參見

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads