多態 | Polymorphism

2024-10-20
GRASP

VIP、老人、兒童、學生...還有沒有第五種折扣...

VIP、老人、兒童、學生...還有沒有第五種折扣...,圖片來源

情境

多個情境會有各自不同的邏輯,通常會使用 if else 或 switch 去進行條件判斷。

情境如果有多個步驟,例如以下的情境,根據不同的客戶類型,在結帳時,需要依照客戶類型去判斷折扣與贈品,一個直觀的寫法如下:

csharp
📝copy
                            
// 結帳
public double Checkout(List commodities, double totalBeforeDiscount, string customerType)
{
        // 新增贈品
        commodities.Add(GetGift(customerType));
        
        // 計算折扣後的
        return CalculatePrice(totalBeforeDiscount, customerType);
}


public double CalculatePrice(double originalPrice, string customerType)
{
    if (customerType == "VIP")
    {
        return originalPrice * 0.8;
    }
    else if (customerType == "Normal")
    {
        return originalPrice;
    }
    else if (customerType == "Senior")
    {
        return originalPrice * 0.9;
    }
    else
    {
        throw new ArgumentException("無效的顧客類型");
    }
}

public string GetGift(string customerType)
{
    if (customerType == "VIP")
    {
        return "高級禮盒";
    }
    else if (customerType == "Senior")
    {
        return "保健食品";
    }
    else
    {
        return "無贈品";
    }
}
                
                            
                        

這種簡單直觀的方法會有兩種問題,主要是出現在情境的新增時:

  • 我們每個步驟需要判斷一次情境的類型,VIP、Senior...
  • 多個情境都擺在同一個函式裡面,提高了改錯的機會。

多態

“ 條件變化是程序的一個基本主題,如果使用 if-then-else 或 case 語句的條件邏輯來設計,那麼當出現新的變化時,則需要修改這些 case 邏輯,這些邏輯通常遍佈各處。 ”

― Craig Larman, Applying UML and Patterns 3rd, ch25.1

利用多態,將一個情境類型作為一個物件進行管理,而該物件有對應的方法,修改之後如下:

csharp
📝copy
                            
public class CheckoutService
{
    public double Checkout(List commodities, double totalBeforeDiscount, ICustomer customer)
    {
        // 新增贈品
        commodities.Add(customer.GetGift());

        // 計算折扣後的總價
        return customer.CalculatePrice(totalBeforeDiscount);
    }
}

public class VIPCustomer : ICustomer
{
    public double CalculatePrice(double originalPrice) => originalPrice * 0.8;
    public string GetGift() => "高級禮盒";
}

public class NormalCustomer : ICustomer
{
    public double CalculatePrice(double originalPrice) => originalPrice;
    public string GetGift() => "無贈品";
}

public class SeniorCustomer : ICustomer
{
    public double CalculatePrice(double originalPrice) => originalPrice * 0.9;
    public string GetGift() => "保健食品";
}

                
                            
                        

將一個情境類型作為一個物件,將判斷次數只限縮於判斷情境而已,另外修改與新增步驟實作也不會干擾到其它情境

不能忽略條件判斷是解決問題最直觀的方式,兩種方法優缺點如下所示:

條件判斷
  • 優點:簡單直接,易於理解。
  • 缺點:新增情境修改繁瑣。
多態
  • 優點:易於擴展,維護成本低。
  • 缺點:需要進行抽象,增加設計與理解成本。

與防止變異

識別變化,並在這之上創建的介面,這是防止變異模式的主旋律,多態同樣也利用介面並且搭配封裝情境,降低程式碼的耦合性、提高擴展性。

雖然解決方法相同,也就是程式碼層面幾乎一致,但是對於使用的上下文、情境有所不同。

  • 防止變異在乎自身模組的改動不會影響到外部模組,是一種設計模組的模式。
  • 多態主要在乎封裝情境,避免四處判斷情境類型

作者心得

寫這篇文對我來講是很艱難的一步,因為多態從愛爾蘭到契丹無人不知、無人不曉,對於一個大家都知道的事情,能寫出個甚麼對我來講是挺難的,尤其是寫完防止變異之後。

對了! 防止變異 ! 於是消滅 if else 跟與防止變異不同就變成主題了。


參考資料

也可以看看以下文章