最近比较忙,有段时间没有更新设计模式的进度了。今天继续学习组合设计模式。
组合模式的例子我们继续延续上篇《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(" " + 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();