《設計模式》之Creational模式:Abstract Factory

對象創建:Abstract Factory



目的

提供一個可以創建一族關聯的或者依賴的對象,而不需要指定它們具體的類。

有時也可稱爲

Kit

驅動

假設有一個用戶接口工具箱,它可以支持多種不同的觀感標準,例如Motif和Presentation Manager。不同的觀感標準定義了不同的外觀和用戶界面行爲,例如滾動條、窗口和按鈕。要想在不同的觀感標準之間方便的遷移,一個應用不應該寫死自己的widget。在整個應用裏到處實例化某一種觀感標準的widget,導致後期很難修改爲其它的觀感標準。

我們可以使用一個抽象的WidgetFactory來解決這個問題,WidgetFactory爲每種基本的widget聲明瞭創建接口。同樣,每一種widget也有一個相應的抽象類,具體的子類會針對不同的觀感標準的widget具體的實現。WidgetFactory提供的接口可以真對每一個抽象的widget類創建widget對象。客戶端可以調用這些接口來獲得widget實例,但是客戶端並不知道它們具體用了哪一個類。因此,客戶端可以獨立於觀感標準。

這裏寫圖片描述

對於每個觀感標準,都有一個WidgetFactory與之對應。每一個子類實現了具體的操作來創建該觀感標準的widget。例如,在MotifWidgetFactory中的CreateScrollBar操作可以實例化並且返回一個Motif的滾動條。客戶端僅僅通過WidgetFactory接口來創建widget,而無需瞭解到底是哪種觀感標準類實現了這個widget。也就是,客戶端直接交互的是抽象類的接口,而不是某一個具體的類。

WidgetFactory同樣加強了某一標準下,不同widget的依賴關係。例如,一個Motif滾動條應該和Motif按鈕和Motif文本編輯器同時使用,這種約束關係自然而然的被加強了,因爲它們都通過MotifWidgetFactory來實現。

適用

可以在一下情況下適用Abstract Factory模式:

  1. 系統需要獨立於它的“產品(product)”如何被創建、組成和表示時。
  2. 系統面對不同的產品家族,只需要配置其中一個時。
  3. 一個家族的關聯產品對象需要被一起配合使用時,你需要強化這種限制時。
  4. 你希望提供一個產品類庫,你只想暴露它們的接口,而不是它們的實現時。

結構

這裏寫圖片描述

成員

  • AbstractFactory(WidgetFactory):對創建抽象產品對象的接口的聲明。
  • ConcreteFactory(MotifWidgetFactory,PMWidgetFactory):實現創建具體產品對象的操作。
  • AbstractProduct(Window,ScrollBar):給某種產品對象聲明一個接口。
  • ConcreteProduct(MotifWindow,MotifScrollBar):定義一個產品對象,這個對象可以被其相應的ConcreteFactory創建;實現AbstractProduct接口。
  • Client:只使用AbstractFactory和AbstractProduct提供的接口。

合作

  • 正常情況下只有一個ConcreteFactory實例在運行時被創建。這個具體的工廠可以創建特定的產品。客戶端要想創建不同的產品,需要使用不同的具體工廠。
  • AbstractFactory將創建產品對象的過程交給ConcreteFactory子類。

結果

Abstract Factory模式有以下的優點和責任:

  1. 它隔離了具體的類。Abstract Factory模式幫助你控制應用創建的對象。因爲一個工廠封裝了創建一個產品對象的責任和過程,它將客戶端和類的具體實現隔離開來。客戶端通過抽象接口來操作實例。產品的類的具體名字被隔離在具體的工廠裏,它們不會出現在客戶端中。
  2. 它使得產品家族的切換變得容易。在應用中,具體工廠的類只出現一次,也就是在它被實例化的時候。這就使得在應用中改變具體工廠類變得很容易。應用可以修改具體的工廠,來使用不同的產品配置。因爲一個抽象工廠會包含某一個家族的所有產品,所以整個產品家族可以通過這個抽象工廠一次性修改。在我們的例子中,我們能夠從Motif widget轉換成Presentation Manager widget,只需要將抽象工廠修改成對應的具體的工廠。
  3. 提高了產品的一致性。當一個家族的產品需要一起使用時,對應用而言,應該每次只使用某一個家族的產品。AbstractFactory可以用來加強這種關係。
  4. 不易支持新的產品。擴展已有的抽象工廠來使其能夠創建新品種的產品是不太容易的。因爲在AbstractFactory裏,提供的接口已經固定了能夠創建的產品類型。支持新類型的產品需要擴展工廠的接口,這包括AbstractFactory類和其子類的接口。我們會在下面具體實現時,提供一個解決這個問題的方法。

實現

這裏介紹一下實現Abstract Factory模式的一些技術。

單例模式

一個應用對某個產品家族而言,只需要一個ConcreteFactory實例即可。所以,最好情況下實現爲一個Singleton。

創建product

AbstractFactory只聲明創建產品的接口,具體的創建過程交給對應的ConcreateProduct子類來實現。最常用的方法是對每個產品使用工廠方法Factory Method。一個具體的工廠將會重寫每個工廠方法來生產具體的產品。這種實現方法很簡單,每個產品家族都需要有自己的具體的工廠子類,儘管這些產品家族之間不盡相同。

如果有許多產品家族,concrete factory可以利用Prototype模式來實現。Concrete factory可以利用每個產品的原型實例來初始化。基於Prototype的方法消除了對每個新的產品家族都需要實現新的concrete factory的麻煩。

這裏有一種使用Smalltalk來實現基於Prototype的factory的方法。Concrete factory 將prototype存儲在一個叫partCatalog的字典裏。方法make會獲取這個原型並且進行克隆:

make: partName
    (partCatalog at: partName) copy

Concrete factory有一個向catalog中添加part的方法:

addPart: partTemplate named: partName
    partCatalog at: partName put: partTemplate

可以用一個標識符將Prototype加入factory:

aFactory addPart: aPrototype named: #ACMEWidget

這種基於Prototype的改進可以在一些特殊的編程語言中實現,這些語言將類看做基本的對象,例如Smalltalk、Objective C。在這個例子中,你可以將類看做一種退化的factory,這個factory它只能構建一種product。你可以將product類像變量一樣存儲在concrete factory中,因此factory可以創建各種各樣的product,就像prototype一樣。

定義可擴展元素

AbstractFactory一般會對每個種類的product定義不同的函數,product的種類一般會在函數名稱上顯示出來。添加新的product需要修改Abstract提供的接口和所有相關的類。

一個更爲靈活但是不太安全的做法是給函數添加參數,這些參數決定了這些函數將要構建哪個product。這個參數可以是類標識符、整數、數組或者其它可以指明這個product的標記。這樣,AbstractFactory 只需要一個函數“Make”,它的參數指明瞭需要構建的對象。這種方法已經在先前討論的基於Prototype的factory中討論過。

這個方法在動態類型的語言中更易使用,例如Smalltalk。在靜態語言中如C++,則需要所有的product都有公共的抽象基本類,或者這些product可以被client安全地強制轉化成正確的類型。在Factory Method章節中,會講述如何用C++實現參數化的操作。

即使不需要強制類型轉化,因爲繼承導致的問題依然存在:所有返回給client的product都有着相同的抽象類型和相同的抽象接口。Client將無法區別和安全的推斷這些product的類。如果client需要調用子類的函數,則在這個抽象類中無法使用。儘管可以使用向下類型轉換,例如在C++中的dynamic_cast,但是這不總是可行的,因爲向下投影可能會失敗。這是在設計高靈活性和擴展性接口的是需要考慮的權衡問題。

樣例代碼

我們將使用Abstract Factory模式來創建一個maze,maze遊戲已經在上一章節中討論過。

類MazeFactory可以創建maze的組件。它可以創建room、wall和door。其它程序可以讀取文件中的設計計劃,使用MazeFactory來創建相應的maze。其它程序也可以利用MazeFactory來隨機的創建一個maze。創建maze的程序會將MazeFactory作爲一個變量,從而程序員可以指定room、wall和door的種類。

calss MazeFactory{
public:
    MazeFactory();

    virtual Maze* MakeMaze() const
        {return new Maze;}
    virtual Wall* MakeWall() const
        {return new Wall;}
    virtual Room* MakeRoom(int n) const
        {return new Room(n);}
    virtual Door* MakeDoor(Room* r1, Room* r2) const
        {return new Door(r1, r2);}
};

上一章節中使用的函數CreateMaze函數,創建了一個小maze,有兩個房間,和一個連接這兩個房間的門。CreateMaze寫死了類的名字,使得其難以轉變成其它組件構成的maze。

這裏有一個利用MazeFactory來消除這個缺點的CreateMaze,它將MazeFactory作爲一個參數:

Maze* MazeGame::CreateMaze (MazeFactory& factory){
    Maze* aMaze=factory.MakeMaze();
    Room* r1=factory.MakeRoom(1);
    Room* r2=factory.MakeRoom(2);
    Door* aDoor=factory.MakeDoor(r1,r2);

    aMaze->AddRoom(r1);
    aMaze->AddRoom(r2);

    r1->SetSide(North, factory.MakeWall());
    r1->SetSide(East, aDoor);
    r1->SetSide(South, factory.MakeWall());
    r1->SetSide(West, factory.MakeWall());
    r2->SetSide(North, factory.MakeWall());
    r2->SetSide(East, factory.MakeWall());
    r2->SetSide(South, factory.MakeWall);
    r2->SetSide(West, aDoor);
    return aMaze;
}

我們可以創建EnchantedMazeFactory,一個魔法迷宮的factory,是MazeFactory的子類。EnchantedMazeFactory將會重寫不同的成員函數,同時返回不同的Room、Wall的子類。

class EnchantedMazeFactory: public MazeFactory{
public:
    EnchantedMazeFactory();

    virtual Room* MakeRoom(int n) const
    {return new EnchantedRoom(n,CastSpell());}

    virtual Door* MakeDoor(Room* r1, Room* r2) const
    {return new DoorNeedingSpell(r1,r2);}

protected:
    Spell* CastSpell() const;
}

假如我們想要一個可以在room放置炸彈的迷宮遊戲,如果這個炸彈爆炸了,它將會損耗牆壁。我們可以新建一個Room的子類來追蹤這個room裏是否有個炸彈和炸彈是否已經爆炸。我們也需要一個Wall的子類來追蹤牆所受傷害。我們將這兩個類稱作RoomWithABomb和BombedWall。

我們最後定義的是一個BombedMazeFactory,一個MazeFactory的子類,它可以確保wall都是BombedWall,room爲RoomWithABomb。BombedMazeFactory只需要重寫兩個函數:

Wall* BombedMazeFactory::MakeWall()const{
    return new BombedWall;
}

Room* BombedMazeFactory::MakeRoom(int n)const{
    return new RoomWithABomb(n);
}

要建立一個有炸彈的迷宮,我們可以簡單的使用CreateMaze,將BombedMazeFactory作爲參數傳遞給它。

MazeGame game;
BombedMazeFactory factory;
game.CreateMaze(factory);

CreateMaze也可以使用EnchantedMazeFactory來創建一個魔法迷宮。

注意到MazeFactory是一個factory方法集合,這是比較常見的實現Abstract Factory 模式的方式。這裏,MazeFactory不是一個抽象類;因此它即作爲AbstractFactory又作爲ConcreteFactory。這又是一個比較常見的實現Abstract Factory的方法。因爲MazeFactory是一個具體類,包含了所有的factory方法。創建一個新的Factory只需要設置成MazeFactory的子類,重寫一些必要的方法。

CreateMaze利用SetSide函數來指定room的邊。如果RoomWithABomb需要訪問BombedWall子類特有的成員,則需要將Wall向下投影到BombedWall。這種投影是安全的,Room中的Wall確實就是BombedWall,因爲他們都是通過BombedMazeFactory創建的。

已知的使用

Interviews使用“Kit”後綴來表示AbstractFactory類,它定義了WidgetKit和DialogKit抽象factory來生成不同觀感的UI組件。Interview也包含了一個LayoutKit,可以根據需求生成不同的組合對象。例如,一個水平layout可能根據文本方向,需要不同的組成對象。

ET++使用AbstractFactory模式來實現跨平臺(如,X Windows 和SunView)的可移植性。WindowSystem抽象基礎類定義了創建窗口組件的接口(如MakeWindow,MakeFont,MakeColor)。具體的子類根據不同的窗口系統實現相應的接口。在運行時,ET++創建一個WindowSystem子類,它會負責創建系統組件對象。

相關模式

AbstractFactory 類 通常使用Factory Method來實現,但是也可以使用Prototype來實現。一個具體的factory經常是一個Singleton。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章