抽象工廠

抽象工廠模式(英語:Abstract factory pattern)是一種軟件開發設計模式。抽象工廠模式提供了一種方式,可以將一組具有同一主題的單獨的工廠封裝起來。在正常使用中,客戶端程式需要建立抽象工廠的具體實現,然後使用抽象工廠作為介面來建立這一主題的具體對象。客戶端程式不需要知道(或關心)它從這些內部的工廠方法中獲得對象的具體類型,因為客戶端程式僅使用這些對象的通用介面。抽象工廠模式將一組對象的實現細節與他們的一般使用分離開來。

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

舉個例子來說,比如一個抽象工廠類叫做DocumentCreator(文件建立器),此類提供建立若干種產品的介面,包括createLetter()(建立信件)和createResume()(建立簡歷)。其中,createLetter()返回一個Letter(信件),createResume()返回一個Resume(簡歷)。系統中還有一些DocumentCreator的具體實現類,包括FancyDocumentCreatorModernDocumentCreator。這兩個類對DocumentCreator的兩個方法分別有不同的實現,用來建立不同的「信件」和「簡歷」(用FancyDocumentCreator的實例可以建立FancyLetterFancyResume,用ModernDocumentCreator的實例可以建立ModernLetterModernResume)。這些具體的「信件」和「簡歷」類均繼承自抽象類,即LetterResume類。客戶端需要建立「信件」或「簡歷」時,先要得到一個合適的DocumentCreator實例,然後呼叫它的方法。一個工廠中建立的每個對象都是同一個主題的(「fancy」或者「modern」)。客戶端程式只需要知道得到的對象是「信件」或者「簡歷」,而不需要知道具體的主題,因此客戶端程式從抽象工廠DocumentCreator中得到了LetterResume類的參照,而不是具體類的對象參照。

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

使用抽象工廠模式,能夠在具體工廠變化的時候,不用修改使用工廠的客戶端代碼,甚至是在執行時。然而,使用這種模式或者相似的設計模式,可能給編寫代碼帶來不必要的複雜性和額外的工作。正確使用設計模式能夠抵消這樣的「額外工作」。

定義

抽象工廠模式的實質是「提供介面,建立一系列相關或獨立的對象,而不指定這些對象的具體類。」[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();
	}
}

適用性

在以下情況可以考慮使用抽象工廠模式:

  • 一個系統要獨立於它的產品的建立、組合和表示時。
  • 一個系統要由多個產品系列中的一個來組態時。
  • 需要強調一系列相關的產品對象的設計以便進行聯合使用時。
  • 提供一個產品類別館,而只想顯示它們的介面而不是實現時。

優點

  1. 具體產品從客戶代碼中被分離出來
  2. 容易改變產品的系列
  3. 將一個系列的產品族統一到一起建立

缺點

  1. 在產品族中擴充新的產品是很困難的,它需要修改抽象工廠的介面

參考文獻

  1. ^ [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=值 (幫助)
  2. ^ [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. ^ 3.0 3.1 Abstract Factory: Implementation (HTML). OODesign.com. [2012-05-16]. (原始內容存檔於2021-03-10) (英語). 

參見