抽象工廠
抽象工廠模式(英語:Abstract factory pattern)是一種軟件開發設計模式。抽象工廠模式提供了一種方式,可以將一組具有同一主題的單獨的工廠封裝起來。在正常使用中,客戶端程序需要創建抽象工廠的具體實現,然後使用抽象工廠作為接口來創建這一主題的具體對象。客戶端程序不需要知道(或關心)它從這些內部的工廠方法中獲得對象的具體類型,因為客戶端程序僅使用這些對象的通用接口。抽象工廠模式將一組對象的實現細節與他們的一般使用分離開來。
舉個例子來說,比如一個抽象工廠類叫做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
類的引用,而不是具體類的對象引用。
「工廠」是創建產品(對象)的地方,其目的是將產品的創建與產品的使用分離。抽象工廠模式的目的,是將若干抽象產品的接口與不同主題產品的具體實現分離開。這樣就能在增加新的具體工廠的時候,不用修改引用抽象工廠的客戶端代碼。
使用抽象工廠模式,能夠在具體工廠變化的時候,不用修改使用工廠的客戶端代碼,甚至是在運行時。然而,使用這種模式或者相似的設計模式,可能給編寫代碼帶來不必要的複雜性和額外的工作。正確使用設計模式能夠抵消這樣的「額外工作」。
定義
編輯抽象工廠模式的實質是「提供接口,創建一系列相關或獨立的對象,而不指定這些對象的具體類。」[1]
使用
編輯具體的工廠決定了創建對象的具體類型,而且工廠就是對象實際創建的地方(比如在C++中,用「new」操作符創建對象)。然而,抽象工廠只返回一個指向創建的對象的抽象引用(或指針)。
這樣,客戶端程序調用抽象工廠引用的方法,由具體工廠完成對象創建,然後客戶端程序得到的是抽象產品的引用。如此使客戶端代碼與對象的創建分離開來。[2]
因為工廠僅僅返回一個抽象產品的引用(或指針),所以客戶端程序不知道(也不會牽絆於)工廠創建對象的具體類型。然而,工廠知道具體對象的類型;例如,工廠可能從配置文件中讀取某種類型。這時,客戶端沒有必要指定具體類型,因為已經在配置文件中指定了。通常,這意味着:
- 客戶端代碼不知道任何具體類型,也就沒必要引入任何相關的頭文件或類定義。客戶端代碼僅僅處理抽象類型。工廠確實創建了具體類型的對象,但是客戶端代碼僅使用這些對象的抽象接口來訪問它們。[3]
- 如果要增加一個具體類型,只需要修改客戶端代碼使用另一個工廠即可,而且這個修改通常只是一個文件中的一行代碼。不同的工廠創建不同的具體類型的對象,但是和以前一樣返回一個抽象類型的引用(或指針),因此客戶端代碼的其他部分不需要任何改動。這樣比修改客戶端代碼創建新類型的對象簡單多了。如果是後者的話,需要修改代碼中每一個創建這種對象的地方(而且需要注意的是,這些地方都知道對象的具體類型,而且需要引入具體類型的頭文件或類定義)。如果所有的工廠對象都存儲在全局的單例對象中,所有的客戶端代碼到這個單例中訪問需要的工廠,那麼,更換工廠就非常簡單了,僅僅需要更改這個單例對象即可。[3]
結構
編輯LePUS3圖
編輯GuiFactory
接口中的createButton
方法返回Button
類型的對象。返回Button
的哪種實現依賴於使用GuiFactory
的哪種實現。
需要注意的是,為了簡潔起見,以上類圖僅僅展示了創建一個類型對象的工廠。而在抽象工廠模式中,通常一個工廠能夠創建若干種不同類型的對象。
代碼舉例
編輯假設我們有兩種產品接口 Button 和 Border ,每一種產品都支持多種系列,比如 Mac 系列和 Windows 系列。這樣每個系列的產品分別是 MacButton, WinButton, MacBorder, WinBorder 。為了可以在運行時刻創建一個系列的產品族,我們可以為每個系列的產品族建立一個工廠 MacFactory 和 WinFactory 。每個工廠都有兩個方法 CreateButton 和 CreateBorder 並返回對應的產品,可以將這兩個方法抽象成一個接口 AbstractFactory 。這樣在運行時刻我們可以選擇創建需要的產品系列。
我們的產品結構是這樣的
class Button; // Abstract Class
class MacButton: public Button {};
class WinButton: public Button {};
class Border; // Abstract Class
class MacBorder: public Border {};
class WinBorder: public Border {};
對應的工廠是這樣的
class AbstractFactory {
public:
virtual Button* CreateButton() =0;
virtual Border* CreateBorder() =0;
};
class MacFactory: public AbstractFactory {
public:
MacButton* CreateButton() { return new MacButton; }
MacBorder* CreateBorder() { return new MacBorder; }
};
class WinFactory: public AbstractFactory {
public:
WinButton* CreateButton() { return new WinButton; }
WinBorder* CreateBorder() { return new WinBorder; }
};
那麼客戶可以根據需要選擇 Mac 風格或者 Win 風格來創建 Button 或 Border
AbstractFactory* fac;
switch (style) {
case MAC:
fac = new MacFactory;
break;
case WIN:
fac = new WinFactory;
break;
}
Button* button = fac->CreateButton();
Border* border = fac->CreateBorder();
<?php
/***************************************************************************
* AbstractFactory.php
* -------------------
* Time : 2006-11-11
* Coder : rollenc(http://www.rollenc.com)
* syre(http://syre.blogbus.com)
***************************************************************************/
abstract class AbstractFactory {
abstract public function CreateButton();
abstract public function CreateBorder();
}
class MacFactory extends AbstractFactory{
public function CreateButton()
{
return new MacButton();
}
public function CreateBorder()
{
return new MacBorder();
}
}
class WinFactory extends AbstractFactory{
public function CreateButton()
{
return new WinButton();
}
public function CreateBorder()
{
return new WinBorder();
}
}
class Button{}
class Border{}
class MacButton extends Button{
function __construct()
{
echo 'MacButton is created' . "\n";
}
}
class MacBorder extends Border{
function __construct()
{
echo 'MacBorder is created' . "\n";
}
}
class WinButton extends Button{
function __construct()
{
echo 'WinButton is created' . "\n";
}
}
class WinBorder extends Border{
function __construct()
{
echo 'WinBorder is created' . "\n";
}
}
?>
那麼客戶可以根據需要選擇 Mac 風格或者 Win 風格的 Button 或 Border 來創建
<?
$type = 'Mac'; //value by user.
if(!in_array($type, array('Win','Mac')))
die('Type Error');
$factoryClass = $type.'Factory';
$factory=new $factoryClass;
$factory->CreateButton();
$factory->CreateBorder();
?>
- 使用上面的例子
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();
}
}
適用性
編輯在以下情況可以考慮使用抽象工廠模式:
- 一個系統要獨立於它的產品的創建、組合和表示時。
- 一個系統要由多個產品系列中的一個來配置時。
- 需要強調一系列相關的產品對象的設計以便進行聯合使用時。
- 提供一個產品類庫,而只想顯示它們的接口而不是實現時。
優點
編輯- 具體產品從客戶代碼中被分離出來
- 容易改變產品的系列
- 將一個系列的產品族統一到一起創建
缺點
編輯- 在產品族中擴展新的產品是很困難的,它需要修改抽象工廠的接口
參考文獻
編輯- ^ [Erich]; Richard Helm, Ralph Johnson, John M. Vlissides. Design Patterns: Abstract Factory (HTML). informIT. 2009-10-23 [2012-05-16]. (原始內容存檔於2021-03-22) (英語).
Object Creational: Abstract Factory: Intent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
請檢查|author-link1=
值 (幫助) - ^ [David]. Object Design for the Perplexed (HTML). The Code Project. 2009-10-23 [2012-05-16]. (原始內容存檔於2011-02-21) (英語).
The factory insulates the client from changes to the product or how it is created, and it can provide this insulation across objects derived from very different abstract interfaces.
請檢查|author-link1=
值 (幫助) - ^ 3.0 3.1 Abstract Factory: Implementation (HTML). OODesign.com. [2012-05-16]. (原始內容存檔於2021-03-10) (英語).
參見
編輯