重溫《深入淺出設計模式》合成模式 (Book Review of Head First Design Pattern, Composite Pattern)
2020-07-07
合成模式就是將類別建構為樹狀的關係,於是樹資料結構的階層關係、遞迴走訪便可以運用在設計系統上。同時合成模式可以與反覆器做結合,客製出強大的類別,保持新增的彈性同時也兼具走訪的便利。
合成模式
Compose objects into tree structures to represent whole-part hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
After Pattern
範例是以餐廳菜單為例,如何表現出菜單中的菜單(晚餐菜單需要包括甜點菜單),藉由合成模式, MenuComponent 為提供 Menu 與 MenuItem 實作的抽象類別,Menu 則是作為聚合類別,其中包括一個 MenuComponent 型別的聚合物類,作為子聚合,也是合成模式的精華所在; MenuItem 則相當於樹狀資料結構中的 Leaf,對應則是餐廳中具體的品項。
合成模式打開了對於設計模式的想像,結合資料結構實作精神與應用方式,從模式的層級去解決系統的問題。
MenuComponent.cs (Component)
public abstract class MenuComponent
{
public virtual void Add(MenuComponent component)
{
throw new NotImplementedException();
}
public virtual void Remove(MenuComponent component)
{
throw new NotImplementedException();
}
public virtual MenuComponent GetChild(int i)
{
throw new NotImplementedException();
}
public virtual string Name{ get;}
public virtual string Description { get; }
public virtual bool Vegetarian { get; }
public virtual double Price{ get; }
public virtual void Print()
{
throw new NotImplementedException();
}
}
Menu.cs (Composite)
public class Menu : MenuComponent
{
List<MenuComponent> _components = new List<MenuComponent>();
public Menu(string name, string description)
{
Name = name;
Description = description;
}
public override string Name { get; }
public override string Description { get; }
public override void Add(MenuComponent component)
{
_components.Add(component);
}
public override MenuComponent GetChild(int i)
{
return base.GetChild(i);
}
public override void Print()
{
Console.WriteLine($"==={Name}===");
foreach (var menuComponent in _components)
{
menuComponent.Print();
}
}
public override void Remove(MenuComponent component)
{
_components.Remove(component);
}
}
MenuItem.cs (Leaf)
public class MenuItem : MenuComponent
{
public MenuItem(string name, string description, double price, bool isveg)
{
Name = name;
Description = description;
Price = price;
Vegetarian = isveg;
}
public override string Name { get; }
public override string Description { get; }
public override double Price { get; }
public override bool Vegetarian { get; }
public override void Print()
{
Console.WriteLine($"{Name} : {Price} {(Vegetarian ? "Veg" : "Meat")} - {Description}");
}
}
Program.cs
var breakfast = new Menu("Breakfast", "BK Store");
var lunch = new Menu("Lunch", "Lunch Store");
var dinner = new Menu("Dinner", "Ding Store");
var dessert = new Menu("Dessert", "Ding Store");
var menu = new Menu("All", "");
breakfast.Add(new MenuItem("Waffles", "Butterscotch waffles", 140, false));
breakfast.Add(new MenuItem("Corn Flakes", "Kellogs", 80, true));
lunch.Add(new MenuItem("Burger", "Cheese and Onion Burger", 250, true));
lunch.Add(new MenuItem("Sandwich", "Chicken Sandwich", 280, false));
dinner.Add(new MenuItem("Pizza", "Cheese and Tomato Pizza", 210, true));
dinner.Add(new MenuItem("Fries", "Cheese Fries", 100, true));
dinner.Add(new MenuItem("Pasta", "Chicken Pasta", 280, false));
dessert.Add(new MenuItem("Ice Cream", "Vanilla and Chocolate", 120, true));
dessert.Add(new MenuItem("Cake", "Choclate Cake Slice", 180, false));
dinner.Add(dessert);
menu.Add(breakfast);
menu.Add(lunch);
menu.Add(dinner);
menu.Print();
小結
✔️將變動的部分封裝起來
✔️類別應該對需求保持開放;應該對既有的程式碼修改保持關閉
Leaf、Menu 都是會變動的需求,封裝成實作 MenuComponent 的類別,讓擴充與變動保持彈性
✔️多用合成,少用繼承
✔️讓需要互動的物件之間的關係鬆綁
合成模式使用合成的方式讓類別聚合的關係鬆綁
✔️針對介面撰寫,而非針對實踐方式撰寫。
聚合的物件共同實作 MenuComponent 類別
✔️讓物件保有最少的知識
✔️單一類別單一責任
每個 MenuComponent 只認識自己的子聚合物件