3. Abstract Factory


Posted by WayneCheng on 2021-01-10

關於 Abstract Factory 本篇將討論以下幾個問題

1. 關於 Abstract Factory

2. UML

3. 將 UML 轉為程式碼

4. 情境


測試環境:

OS:Windows 10
IDE:Visual Studio 2019


1. 關於 Abstract Factory

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

by Gang of Four

  • 提供一個用於創建相關或依賴物件家族(父類別 & 子類別)的接口,且無需指定其具體類別

Abstract Factory(抽象工廠)屬於創建型(Creational Patterns),當遇到需要取得兩個以上相互關聯的實體時,使用 Abstract Factory 來取得實體並將依賴邏輯處理後回傳結果,Abstract Factory 為 Factory Method 的加強版,同時使用 class 的數量也是加強版。

優點:

  • 符合 單一職責原則(Single Responsibility Principle)
  • 符合 開閉原則(Open Closed Principle)

缺點:

  • 會增加許多 class 造成程式複雜度增加

2. UML


Class 間關聯:

  • Product A1 / A2 繼承 AbstractProductA
  • Product B1 / B2 繼承 AbstractProductB
  • ConcreteFactory 1 / 2 繼承 AbstractFactory
  • Client 關聯 AbstractProduct A / B & AbstractFactory

Class:

  • Client:呼叫端,透過 Factory 取得多個 Product 實體,並處理其關聯邏輯
  • AbstractProduct:處理邏輯的抽象類別或介面
  • Product:處理邏輯實作
  • AbstractFactory:抽象工廠的抽象類別或介面
  • ConcreteFactory:抽象工廠實作,用來創建處理邏輯實體

3. 將 UML 轉為程式碼

處理邏輯介面

/// <summary>
/// 處理邏輯介面 A
/// </summary>
public interface IProductA
{
}

/// <summary>
/// 處理邏輯的介面 B
/// </summary>
public interface IProductB
{
    void Interact(IProductA a);
}

處理邏輯實作

/// <summary>
/// 處理邏輯介面 A 實作 A1
/// </summary>
public class ProductA1 : IProductA
{
}

/// <summary>
/// 處理邏輯介面 B 實作 B1
/// </summary>
class ProductB1 : IProductB
{
    public void Interact(IProductA a)
    {
        Console.WriteLine($"{this.GetType().Name} interacts with {a.GetType().Name}");
    }
}

/// <summary>
/// 處理輯介面 A 實作 A2
/// </summary>
public class ProductA2 : IProductA
{
}

/// <summary>
/// 處理邏輯介面 B 實作 B2
/// </summary>
class ProductB2 : IProductB
{
    public void Interact(IProductA a)
    {
        Console.WriteLine($"{this.GetType().Name} interacts with {a.GetType().Name}");
    }
}

抽象工廠的介面

/// <summary>
/// 抽象工廠介面
/// </summary>
public interface IFactory
{
    IProductA CreateProductA();
    IProductB CreateProductB();
}

抽象工廠實作

/// <summary>
/// 抽象工廠實作 1
/// </summary>
public class ConcreteFactory1 : IFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA1();
    }
    public IProductB CreateProductB()
    {
        return new ProductB1();
    }
}

/// <summary>
/// 抽象工廠實作 2
/// </summary>
public class ConcreteFactory2 : IFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA2();
    }
    public IProductB CreateProductB()
    {
        return new ProductB2();
    }
}

呼叫端,透過 factory 取得 Product 實作,並處理 Product 間依賴邏輯

/// <summary>
/// 呼叫端,透過 factory 取得 Product 實作,並處理 Product 間依賴邏輯
/// </summary>
public class Client
{
    private IProductA _productA;
    private IProductB _productB;

    public Client(IFactory factory)
    {
        _productB = factory.CreateProductB();
        _productA = factory.CreateProductA();
    }

    public void Run()
    {
        _productB.Interact(_productA);
    }
}
  1. 建立工廠 factory 分別傳入 client
  2. 透過 client 處理 product 間邏輯
static void Main(string[] args)
{
    Default.IFactory factory1 = new Default.ConcreteFactory1();
    Default.Client client1 = new Default.Client(factory1);
    client1.Run();

    Default.IFactory factory2 = new Default.ConcreteFactory2();
    Default.Client client2 = new Default.Client(factory2);
    client2.Run();

    Console.ReadLine();
}

執行結果

ProductB1 interacts with ProductA1
ProductB2 interacts with ProductA2

4. 情境

我們接到了一個付款且要依據付款方式打折的需求

  • 需要能支援現有的兩種付款方式(現金、ApplePay)
  • 要依據付款方式提供折扣
  • 且未來可能會有更多不同的付款方式

建立付款 & 折扣介面

/// <summary>
/// 付款方式介面
/// </summary>
public interface IPayment
{
    void Pay(int amount);
}

/// <summary>
/// 折扣介面
/// </summary>
public interface IDiscount
{
    void Interact(IPayment payment, int amount);
}

實作付款介面

/// <summary>
/// 實作付款介面:現金
/// </summary>
public class Cash : IPayment
{
    public void Pay(int amount)
    {
        Console.WriteLine($"使用 現金 付款 {amount} 元");
    }
}

/// <summary>
/// 實作付款介面:ApplePay
/// </summary>
public class ApplePay : IPayment
{
    public void Pay(int amount)
    {
        Console.WriteLine($"使用 ApplePay 付款 {amount} 元");
    }
}

實作折扣介面

/// <summary>
/// 實作折扣介面:現金
/// </summary>
class DiscountCash : IDiscount
{
    public void Interact(IPayment payment, int amount)
    {
        // 使用現金無折扣
        payment.Pay(amount);
    }
}

/// <summary>
/// 實作折扣介面:ApplePay
/// </summary>
class DiscountApplePay : IDiscount
{
    public void Interact(IPayment payment, int amount)
    {
        // 使用 ApplePay 打九折
        var newAmount = (int)(amount * 0.9);
        payment.Pay(newAmount);
    }
}

抽象工廠介面

/// <summary>
/// 抽象工廠介面
/// </summary>
public interface IFactory
{
    IPayment CreatePayment();
    IDiscount CreateDiscount();
}

分別實作「現金付款工廠」&「ApplePay 付款工廠」

/// <summary>
/// 現金付款工廠
/// </summary>
public class CashFactory : IFactory
{
    public IPayment CreatePayment()
    {
        return new Cash();
    }
    public IDiscount CreateDiscount()
    {
        return new DiscountCash();
    }
}

/// <summary>
/// ApplePay 付款工廠
/// </summary>
public class ApplePayFactory : IFactory
{
    public IPayment CreatePayment()
    {
        return new ApplePay();
    }
    public IDiscount CreateDiscount()
    {
        return new DiscountApplePay();
    }
}

呼叫端,依據傳入的工廠取得付款方式 & 折扣方式

/// <summary>
/// 取得付款 & 折扣實作,並可進行付款動作
/// </summary>
public class Client
{
    private IPayment _payment;
    private IDiscount _discount;

    public Client(IFactory factory)
    {
        _discount = factory.CreateDiscount();
        _payment = factory.CreatePayment();
    }

    public void Pay(int amount)
    {
        _discount.Interact(_payment, amount);
    }
}
  1. 建立工廠 cashFactory & applePayFactory 分別傳入 Client
  2. 透過 Client 處理付款邏輯 & 折扣邏輯
static void Main(string[] args)
{
    Situation.IFactory cashFactory = new Situation.CashFactory();
    Situation.Client cashClient = new Situation.Client(cashFactory);
    cashClient.Pay(100);

    Situation.IFactory applePayFactory = new Situation.ApplePayFactory();
    Situation.Client applePayClient = new Situation.Client(applePayFactory);
    applePayClient.Pay(100);

    Console.ReadLine();
}

執行結果

使用 現金 付款 100 元
使用 ApplePay 付款 90 元

完整程式碼

GitHub:Creational_02_AbstractFactory


總結

Abstract Factory 比 Factory Method 複雜些,滿難一眼就理解其間的差異,在此建議可以試著先將 UML 內 class 之間的關聯釐清後,依據自己的理解將 UML 畫出來,藉此加深對各 Design Pattern 的印象。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

新手上路,若有錯誤還請告知,謝謝


#designpattern #CSharp







Related Posts

5. 頻譜分析

5. 頻譜分析

[重新理解C++] 從 function object 理解惰性編譯

[重新理解C++] 從 function object 理解惰性編譯

關於重新導向的 status code

關於重新導向的 status code


Comments