[Design Pattern] Abstract Factory Pattern

產生"一系列"性質相關或相依的物件

官方說明

提供一個介面,建立相關或相依物件之家族,而不需要明確指定具體類別。

 

主要療效

產生"一系列"性質相關或相依的物件

透過ConcreteFactory1來產生粉紅系列的商品: ProductA1, ProductB1

透過ConcreteFactory2來產生彩橘系列的商品: ProductA2, ProductB2

image

 

適用情境

1. 系統中有多組"相同性質"之物件集合

2. 系統只會使用其中一組"相同性質"之物件集合

3. 集合內之物件間是相關或相依的關係

4. 集合內之物件必定是在一起使用

PS. 如果沒有多組"相同性質"之物件集合需求時,使用靜態工廠即可

 

關鍵心法

1. 建立產品抽象(abstract/interface)類別 

2. 建立各產品之不同系列類別 (繼承產品抽象類別)

3. 建立抽象工廠 (定義一整組生產商品物件的方法)

4. 建立各系列專屬的工廠 (繼承抽象工廠且實作所有方法[產生此系列產品物件實體])

 

應用聯想

想像遊戲軟體開發需求,初始畫面可依遊戲玩家國籍來顯示遊戲場景,場景需呈現出具該國風格之街道景色。分析一下,各國的街景似乎大同小異,不外乎市街道、招牌、房子等物件存在,且各場景中物件都具有相關性(日式街道+日式招牌+日式屋型),該些特性似乎都滿符合此模式之適用情境,因此我們將實際演練如何透過抽象工廠模式的協助,產出這一系列相關性物件。

image

 

首先來檢查是否合乎適用情境:

1. 系統中有多組"相同性質"之物件集合

    台灣場景 or 日本場景

2. 系統只會使用其中一組"相同性質"之物件集合

    只會使用一種場景顯示

3. 集合內之物件間是相關或相依的關係

    日本街景 只與 日本招牌 互相依賴作用

4. 集合內之物件必定是在一起使用

    不會一個使用日本街景 另一個使用台灣招牌吧!!

 

搭配關鍵心法來實現遊戲場景的抽象工廠:

1. 建立產品抽象(abstract/interface)類別 

    筆者希望遊戲場景中有街道並且佈滿著招牌,所以先定義出這兩者之介面類別

    其中需要實作 GetDescription() 方法來簡單描述實際呈現出的樣貌,如下:

public interface ISignboardScene
{
    // 取得招牌描述字串
    string GetSignboardDescription();
}

// 街道介面
public interface IStreetScene
{
    // 取得街道描述字串
    string GetStreetDescription();
}
	

2. 建立各產品之不同系列類別 (繼承產品抽象類別)

    招牌: 日本招牌、台灣招牌...

class JapanSignboardScene : ISignboardScene
{
    public string GetSignboardDescription()
    {
        return "日文招牌";
    }
}

class TaiwanSignboardScene : ISignboardScene
{
    public string GetSignboardDescription()
    {
        return "中文招牌";
    }
}

    街道: 日本街道、台灣街道 ...

class JapanStreetScene : IStreetScene
{
    public string GetStreetDescription()
    {
        return "很多日本人走在路上";
    }
}

class TaiwanStreetScene : IStreetScene
{
    public string GetStreetDescription()
    {
        return "很多台灣人走在路上";
    }
}

   

3. 建立抽象工廠 (定義一整組生產商品物件的方法)

    筆者希望一個場景中至少需要街道與招牌來作呈現

    所以繼承場景抽象工廠之子類別皆須實作CreateSignboardScene()與CreateStreetScene()方法

public abstract class AbstractSceneFactory
{
    // 產生招牌
    public abstract ISignboardScene CreateSignboardScene();

    // 產生街道
    public abstract IStreetScene CreateStreetScene(); 
}

4. 建立各系列專屬的工廠 (繼承抽象工廠且實作所有方法[產生此系列產品物件實體])

   日本場景工廠: 需產生 日本街道 + 日本招牌

public class JapanSceneFactory : AbstractSceneFactory
{
    public override ISignboardScene CreateSignboardScene()
    {
        // 產生日本招牌
        return new JapanSignboardScene();
    }

    public override IStreetScene CreateStreetScene()
    {
        // 產生日本街道
        return new JapanStreetScene();
    }
}

   台灣場景工廠: 需產生 台灣街道 + 台灣招牌

public class TaiwanSceneFactory : AbstractSceneFactory
{
    public override ISignboardScene CreateSignboardScene()
    {
        // 產生台灣招牌
        return new TaiwanSignboardScene();
    }

    public override IStreetScene CreateStreetScene()
    {
        // 產生台灣街道
        return new TaiwanStreetScene();
    }
}

 

使用場景抽象工廠來產生不同系列之場景:

首先須建立場景工廠實體(此例為日本場景工廠),透過注入的方式讓ShowDescriptScene()方法中取得工廠實體,並且透過工廠產生一系列場景物件(日式招牌、日式街道);若需要呈現台灣場景時,僅需修改注入台灣廠場景工廠實體即可。

class Program
{
    static void Main(string[] args)
    {
        
        // 建立工廠 (日本場景工廠)
        var factory = new JapanSceneFactory();
        
		// 透過注入(DI)之工廠產生遊戲場景
        ShowSceneDescription(factory);

        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();

    }


    // 顯示場景描述 (DI: 方法注入)
    public static void ShowSceneDescription(AbstractSceneFactory factory)
    {
     
        // 使用抽象工廠 產出的同系列產品
        ISignboardScene signboardtScene = factory.CreateSignboardScene();
        IStreetScene streetScene = factory.CreateStreetScene();

        // 輸出 街景介紹 結果
        string sceneDescription = string.Empty;
        sceneDescription += string.Format("招牌: {0} \n", signboardtScene.GetSignboardDescription());
        sceneDescription += string.Format("街道: {0} \n", streetScene.GetStreetDescription());
        
        Console.WriteLine("街景介紹");
        Console.WriteLine(sceneDescription);


        // 整個程式只與介面相依(AbstractSceneFactory, ISignboardScene, IStreetScene)
        // 與OOP中針對介面寫程式的概念相符
    }
}

image

image

 

後記:

我們可以發現整個程式只與介面相依(AbstractSceneFactory, ISignboardScene, IStreetScene),與OOP中針對介面寫程式的概念相符;另外亦符合OOP中開放封閉原則,成功將ShowSceneDescription透過注入方式進行封閉,將來若須建立美國場景時,只需實作美國招牌(ISignboardScene)與街道(IStreetScene),並實作出美國場景工廠(AbstractSceneFactory)來進行注入,即可輕易地於遊戲中顯示美式風格場景。

 

參考資料

http://www.dotblogs.com.tw/mumbo/archive/2011/06/29/30406.aspx

http://www.cnblogs.com/Terrylee/archive/2005/12/13/295965.html


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !