重溫《深入淺出設計模式》裝飾者模式 (Book Review of Head First Design Pattern, Decorator Pattern)
2020-06-29
以前第一次看到 Python 的 Decorator 時滿滿的黑人問號,後來有機會接觸裝飾者模式才豁然開朗,但也沒有深究,甚至沒有真的用過 Decorator。但最近重溫才發現其實裝飾者模式和 Python 的 Decorator 並不是完全的等號。Python 的 Decorator 更像是一種語法糖,利用 Python 函式 first class 的特性,能夠有更方便包裝函式的語法,可以為函式前後加入邏輯並且重利用程式碼。而要利用 Python 的 Decorator 也可以實踐 Decorator Pattern ,只是兩者並不是相等的,一個是語法模式,但背後有 Pattern 的精神;另一個則是 Pattern。
裝飾者模式
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
裝飾者模式提供了一個有別於繼承的設計方式,讓程式碼不僅得以重複使用且易於擴充。本模式是以咖啡為範例,咖啡作為一種類別,而調味則是其需求,如果要因應各式需求,必須衍生繁瑣的類別。
Before Pattern
目前的設計會有以下問題:
- 如果有悉的配料,就要加入新的方法,並且變更 Beverage 類別中 cost 的邏輯
- 如果需求條件(配料)有特殊需求,例如雙倍、新類型,Beverage 類別就必須配合修改。
public class Beverage
{
private float condimentCost = 0.0;
public bool hasMilk()
{
...
}
public bool hasMocha()
{
...
}
public double cost()
{
if (hasMilk())
condimentCost += milkCost;
if (hasMocha())
condimentCost += mochaCost;
return condimentCost;
}
}
public class DarkRoast : Beverage
{
public double cost()
{
return 1.99 + super.cost();
}
}
After Pattern
而裝飾者模式要求先設計一個類別與裝飾類別之間共通的類別(超類別),並且可以用一個或多個裝飾者包裝物件,最重要的是裝飾者可以在所委派被裝飾者的行為之前或之後,加上自己的行為達到特定目的,裝飾者提供了動態將責任加諸於物件的彈性,相較於繼承更易於擴充。
要實作裝飾者,最重要的就是
public abstract class Beverage
{
private string _description = "unknown beverage";
public string GetDescription()
{
return _description;
}
public abstract void Cost();
}
public abstract class CondimentDecorator : Beverage
{
public abstract string GetDescription();
}
public class Espresso : Beverage
{
public Espresso()
{
_description = "Espresso";
}
public double cost()
{
return 1.99;
}
}
// Decorator 的靈魂,實作與被裝飾者相同的超類別,並且將被裝飾者當成自己的 private 成員,於是可以一層一層的呼叫
public class Mocha : CondimentDecorator
{
private Beverage _beverage;
public Mocha (Beverage beverage)
{
_beverage = beverage;
}
public string GetDescription()
{
return _beberage.GetDescription() + ", Mocha";
}
public double Cost()
{
return 0.2 + _beverage.Cost();
}
}
Beverage beverage = new Espresso();
beverage = new Mocha(beverage);
Console.Writeline(beverage.GetDescription()); // Espresso, Mocha
小結
✔️將變動的部分封裝起來
- 裝飾的類別會衍生,所以個別封裝
✔️多用合成,少用繼承
- 根據裝飾者模式的精神,演算邏輯由合成的類別實作
✔️類別應該對需求保持開放;應該對既有的程式碼修改保持關閉
- 實作新的裝飾類別或者被裝飾類別,並不會互相影響