热门问题
时间线
聊天
视角
工廠方法模式
来自维基百科,自由的百科全书
Remove ads
工廠方法模式(英語:factory method pattern),是一種實現了工廠概念的物件導向設計模式。就像其他建立型模式一樣,它也是處理在不指定對象具體類型的情況下建立對象的問題。

概述
對象建立中的有些過程包括決定建立哪個對象、管理對象的生命周期,以及管理特定對象的建立和銷毀的概念。工廠方法模式的實質是「定義一個建立對象的介面,但讓實現這個介面的類來決定實例化哪個類。工廠方法讓類別的實例化推遲到子類中進行。」[1]
建立一個對象常常需要複雜的過程,所以不適合包含在一個複合對象中。建立對象可能會導致大量的重複代碼,可能會需要複合對象訪問不到的資訊,也可能提供不了足夠級別的抽象,還可能並不是複合對象概念的一部分。工廠方法模式通過定義一個單獨的建立對象的方法來解決這些問題。由子類實現這個方法來建立具體類型的對象。
結構

在上面的UML類圖中,Creator
類要求Product
對象,但不直接的實例化Product1
類。Creator
轉而提及一個獨立的factoryMethod()
來建立一個產品對象,這使得Creator
獨立於被實例化的確切的具體類。Creator
的子類可以精製(redefine)出哪個類要被實例化。在這個例子中,Creator1
子類通過實例化Product1
類來實現抽象的factoryMethod()
。
代碼舉例
下面的迷路園遊戲的Java例子,類似於《設計模式》中的例子,這裡針對迷路園生成演算法而簡化了對房間的四面、門和牆的處置,即一個房間與毗鄰的另一個房間,如果二者連通則其間有開著的一個門,如果二者不連通則其間有一個牆;永遠開著的門通常就表現為房間的這一面沒有牆,如果要將門具象表現出來則附含著其所處在的牆。
抽象類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
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++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#例子:
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
適用性
工廠方法模式,適用於基於介面編程與實現依賴反轉原則。 下列情況可以考慮使用工廠方法模式:
- 建立對象需要大量重複的代碼。可以把這些代碼寫在工廠基礎類別中。
- 建立對象需要訪問某些資訊,而這些資訊不應該包含在複合類中。
- 建立對象的生命周期必須集中管理,以保證在整個程式中具有一致的行為。 對象建立時會有很多參數來決定如何建立出這個對象。
- 建立對象可能是一個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
方法,在方法中建立並返回一個FakeDangerous
(Dangerous
的子類),而這是一個類比對象。這樣,單元測試就可以使用TestFoo
來測試Foo
的功能,從而避免了使用Dangerous
對象帶來的副作用。
參照
參考文獻
參見
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads