抽象工廠
抽象工廠模式(英語: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) (英語).
參見