最近比較忙,有段時間沒有更新設計模式的進度了。今天繼續學習組合設計模式。
組合模式的例子我們繼續延續上篇《Head First設計模式——迭代器模式》的菜單例子,首先聲明下迭代器和組合模式沒有什么關系,他們是兩個不同模式。只是我們在這個菜單例子的組合模式內部會用到迭代器。
迭代器模式中說到兩個餐館合並然后使用迭代器進行統一處理菜單的打印,但是現在有一個新的需求是原來大菜單中我們希望加入子菜單,比如飯后甜點。那么這個時候對於需求模型來說就是類似下面這樣

菜單擁有菜單項,菜單項中可能還擁有子菜單,我們現在要打印菜單。也就是處理每個菜單和菜單項,如何將他們合理的組織起來並統一處理?要解決這個問題,組合模式來實現這一需求。
定義組合模式
組合模式:允許你將對象組合成樹形結構來表現“整體/部分”層次結構。組合能讓客戶以一致的方式處理個別對象以及對象組合。
這個模式能夠創建一個樹形結構,如果我們有了一個樹形結構的菜單、子菜單和可能還帶有菜單項的子菜單,那么任何一個菜單都是一種“組合”。因為它既可以包含其他菜單,也可以包含菜單項。個別對象只是菜單項並未持有其他對象。
利用組合設計菜單
我們需要創建一個組件接口來作為菜單和菜單項的共同接口,讓我們能夠用統一的做法來處理菜單和菜單項,換句話說,我們可以針對菜單或菜單項調用相同的方法。
我們畫出菜單設計的類圖:

MenuComponent:提供接口,讓菜單項和菜單共同使用。我們可能會對方法提供一些默認實現,所以我們可以使用抽象類。
MenuItem:繼承自MenuComponent,覆蓋了它有意義的方法(add,remove不用管)。
Menu:繼承自MenuComponent,覆蓋對它有意義的方法。
實現組合模式
實現菜單組件
public abstract class MenuComponent
{
public virtual void Add(MenuComponent menuComponent) {
throw new NotSupportedException();
}
public virtual void Remove(MenuComponent menuComponent)
{
throw new NotSupportedException();
}
public virtual MenuComponent GetChild(int i)
{
throw new NotSupportedException();
}
public virtual void GetName()
{
throw new NotSupportedException();
}
public virtual string GetDescription()
{
throw new NotSupportedException();
}
public virtual double GetPrice()
{
throw new NotSupportedException();
}
public virtual bool IsVegetarian()
{
throw new NotSupportedException();
}
public virtual void Print()
{
throw new NotSupportedException();
}
}
實現菜單項
public class MenuItme : MenuComponent
{
string name;
string decription;
bool vegetarian;
double price;
public MenuItme(string name, string decription, bool vegetarian, double price)
{
this.name = name;
this.decription = decription;
this.vegetarian = vegetarian;
this.price = price;
}
public override string GetName()
{
return name;
}
public override string GetDescription()
{
return decription;
}
public override double GetPrice()
{
return price;
}
public override bool IsVegetarian()
{
return vegetarian;
}
public override void Print()
{
Console.Write(" " + GetName());
if (IsVegetarian())
{
Console.Write("V" + GetName());
}
Console.WriteLine("," + GetPrice());
Console.WriteLine(" --" + GetPrice());
}
}
實現組合菜單
public class Menu:MenuComponent
{
List<MenuComponent> menuComponents = new List<MenuComponent>();
string name;
string description;
public Menu(string name, string description)
{
this.name = name;
this.description = description;
}
public override void Add(MenuComponent menuComponent)
{
menuComponents.Add(menuComponent);
}
public override void Remove(MenuComponent menuComponent)
{
menuComponents.Remove(menuComponent);
}
public override MenuComponent GetChild(int i)
{
return menuComponents[i];
}
public override string GetName()
{
return name;
}
public override string GetDescription()
{
return description;
}
public override void Print()
{
Console.Write("\n" + GetName());
Console.WriteLine("," + GetDescription());
Console.WriteLine("---------------------");
foreach (var item in menuComponents)
{
item.Print();
}
}
}
這里菜單打印直接用foreach 循環打印菜單組件,如果遇到另外一個菜單對象則進入子菜單打印。此處就是使用迭代器模式,只不過我偷了個懶直接用了foreach,因為list C#已經實現了迭代器,使用foreach語法即可。《C# Foreach循環本質與枚舉器》
測試
MenuComponent breakfastMenu = new Menu("早餐菜單", "早餐供應");
MenuComponent dinnerMenu = new Menu("晚餐菜單", "晚餐供應");
MenuComponent dessertMenu = new Menu("甜點菜單", "晚餐甜點");
MenuComponent allMenus = new Menu("ALL MENUS", "all menus combaind");
//加入菜單
allMenus.Add(breakfastMenu);
allMenus.Add(dinnerMenu);
//加入菜單項
breakfastMenu.Add(new MenuItme("包子", "鮮肉醬肉", false, 2));
dinnerMenu.Add(new MenuItme("牛肉拉面", "拉面配牛肉", false, 15));
dinnerMenu.Add(dessertMenu);
dessertMenu.Add(new MenuItme("夢龍卷", "切件", false, 16));
allMenus.Print();

