组合模式(Composite Pattern)

    xiaoxiao2021-04-16  39

    组合模式:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

    场景:利用组合模式将菜单和其子项打印出来,菜单有三套早餐、午餐、咖啡,其中午餐下还有子菜单甜点。

    //抽象组件类

    //组合中菜单及子项组件的抽象类 public abstract class MenuComponent { //菜单包含的方法 public virtual void add(MenuComponent menuComponent) { throw new NotImplementedException(); } public virtual void remove(MenuComponent menuComponent) { throw new NotImplementedException(); } public virtual MenuComponent getChild(int i) { throw new NotImplementedException(); } //菜单子项包含的方法 public virtual double getPrice() { throw new NotImplementedException(); } public virtual bool isVegetarian() { throw new NotImplementedException(); } //都包含的方法 public virtual string getName() { throw new NotImplementedException(); } public virtual string getDescription() { throw new NotImplementedException(); } public virtual void print() { throw new NotImplementedException(); } }//具体组件类(菜单)

    //菜单组件类 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 (MenuComponent menuComponent in menuComponents) { menuComponent.print(); } } } //具体组件类(菜单项) //菜单项,作为叶节点,没有实现子菜单的方法 public class MenuItem:MenuComponent { string name; string description; bool vegetarian; double price; public MenuItem(string name, string description, bool vegetarian, double price) { this.name = name; this.description = description; this.vegetarian = vegetarian; this.price = price; } public override string getName() { return name; } public override string getDescription() { return description; } public override double getPrice() { return price; } public override bool isVegetarian() { return vegetarian; } public override void print() { Console.Write(" "+getName()); if (isVegetarian()) { Console.Write("(v)"); } Console.Write(", "+getPrice().ToString()); Console.WriteLine( " -- "+getDescription()); } }//操作组合的类

    //可以打印菜单的服务员 public class Waitress { MenuComponent allMenus; public Waitress(MenuComponent allMenus) { this.allMenus = allMenus; } public void printMenu() { allMenus.print(); } }//测试用例

    public void run() { MenuComponent allMenus = createAllMenus(); Waitress waitress = new Waitress(allMenus); waitress.printMenu(); Console.ReadLine(); } private MenuComponent createAllMenus() { MenuComponent allMenus = new Menu("ALL MENUS", "所有的菜单呈上"); MenuComponent pancakeHouseMenu = new Menu("PANCKE HOUSE MENU", "早餐"); MenuComponent dinerMenu = new Menu("DINER MENU", "午餐"); MenuComponent cafeMenu = new Menu("CAFF MENU", "咖啡"); MenuComponent dessertMenu = new Menu("DESSERT MENU", "甜点"); allMenus.add(pancakeHouseMenu); allMenus.add(dinerMenu); allMenus.add(cafeMenu); //加入菜单 pancakeHouseMenu.add(new MenuItem("K&B's薄煎饼早餐", "薄煎饼、氢弹、吐司", true, 2.99)); pancakeHouseMenu.add(new MenuItem("薄煎饼早餐例餐", "薄煎饼带煎蛋、香肠", false, 2.99)); pancakeHouseMenu.add(new MenuItem("蓝莓薄煎饼", "薄煎饼、新鲜蓝莓、蓝莓糖浆", true, 3.49)); pancakeHouseMenu.add(new MenuItem("松饼", "松饼,可以选择蓝莓或草莓", true, 3.59)); dinerMenu.add(new MenuItem("素食BLT", "(煎)培根、生菜&西红柿放在麦皮面包上", true, 2.99)); dinerMenu.add(new MenuItem("BLT", "培根、生菜&西红柿", true, 2.99)); dinerMenu.add(dessertMenu); dessertMenu.add(new MenuItem("苹果派", "苹果派、果酱、冰激凌", true, 1.59)); cafeMenu.add(new MenuItem("爱喝不喝咖啡", "爱喝不喝、咖啡", true, 99999.99)); return allMenus; }//测试结果 总结:

    1.组合模式以单一责任设计原则换取透明性(transparency)。

    2.透明性是:通过让组件的接口同事包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。

    扩展场景:通过服务员打印素食主义者菜单。

    提示:之前已经实现了一个内部迭代器print(),但内部迭代过程外部无法干预,所以要通过组件属性实现筛选,需要实现一个外部的迭代器。

    //组件迭代器类

    //组件迭代器 public class ComponentIterator : IEnumerator { Stack<IEnumerator> stack = new Stack<IEnumerator>(); MenuComponent current = null; public ComponentIterator(IEnumerator iterator) { stack.Push(iterator); } public object Current { get { return current; } } public bool MoveNext() { if (stack.Count == 0) { return false; } else { IEnumerator ienum = stack.Peek(); //获取栈顶迭代器元素 if (stack.Peek().MoveNext()) { //迭代器中存在未遍历到的元素 MenuComponent menuComponet = (MenuComponent)ienum.Current; //取迭代器中的一个元素 if (menuComponet is Menu) { stack.Push(menuComponet.createIterator()); //若元素是菜单,则将该菜单的迭代器入栈 return MoveNext(); //递入本方法,去查找菜单项或完成遍历 } else { current = (MenuComponent)ienum.Current; //若元素是菜单项,则记录改元素到变量current中 return true; //返回存在下一个元素 } } else { stack.Pop(); //若栈顶迭代器已全部遍历则将其弹出 return MoveNext(); //递入本方法,去查找菜单项或完成遍历 } } } public void Reset() { throw new NotImplementedException(); } } //空迭代器

    public class NullIterator:IEnumerator { public object Current { get { return null; } } public bool MoveNext() { return false; } public void Reset() { throw new NotImplementedException(); } } //在 抽象 组件类 MenuComponent中加入一个创建迭代器的 抽象方法。

    public abstract class MenuComponent { ... public abstract IEnumerator createIterator(); }//Menu类实现 createIterator()

    public class Menu:MenuComponent { ... public override System.Collections.IEnumerator createIterator() { return new ComponentIterator(menuComponents.GetEnumerator()); } }//MenuItem 类实现 createIterator()

    public class MenuItem:MenuComponent { ... public override System.Collections.IEnumerator createIterator() { return new NullIterator(); } }  //打印菜单的服务员 public class Waitress { ... public void printVegetarianMenu() { IEnumerator iEnum = allMenus.createIterator(); Console.WriteLine("\nVEGETARIAN MENU\n----"); while (iEnum.MoveNext()) { MenuComponent menuComponent = (MenuComponent)iEnum.Current; if (menuComponent == null) { continue; } try { if (menuComponent.isVegetarian()) { menuComponent.print(); } } catch (NotImplementedException e) { Console.WriteLine(e.Message); } } } }//测试用例

    static void Main(string[] args) { MenuComponent allMenus = createAllMenus(); Waitress waitress = new Waitress(allMenus); waitress.printVegetarianMenu(); Console.ReadLine(); }//测试结果

    总结:外部迭代器必须维护它在遍历中的位置,以便外部客户可以通过MoveNext()和Current来驱动遍历,这个例子中,我们的代码也必须维护组合递归结构的位置。

    转载请注明原文地址: https://ju.6miu.com/read-672980.html

    最新回复(0)