承接上篇博客,我们需要修改所有的菜单,以满足可以添加子菜单,但是我们由于已经把整个项目的框架搭好了,不可能重新实现菜单,所以我们只能修改,那么我们需要做些什么呢?

  • 树形结构,可以容纳菜单、子菜单和菜单项。
  • 可以在这个结构中的每个元素中游走,比如类似于迭代器一样的装置。
  • 可以遍历单个菜单项,比如只遍历甜点菜单,也可以遍历整个菜单所有菜单项。

我们可以使用组合模式来解决这个问题,首先我们看看组合模式的定义。

组合模式定义

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

这个模式可以创建一个树形结构,在同一个结构中处理嵌套菜单和菜单项组。通过将菜单和项放在相同的结构中,我们创建了一个「整体/部分」层次结构,即由菜单和菜单项组成的对象树。

使用一个 Component 接口定义这些组件和叶子节点的操作,比如 add()、remove()、setChild()、operation() 等等。叶子节点也继承了这些方法,但因为它没有子节点,所以这些方法可能没什么意义,但它通过 operation() 方法完成它自己的任务。而组件具有子节点,所以这几个方法都需要使用。而客户可以通过 Component 接口操作组合中的对象。

现在我们来利用组合模式设计菜单。

设计菜单

女侍者是我们的客户,她将使用菜单组件接口访问菜单和菜单项。菜单组件除了提供 getName()、getDescription()、getPrice()、isVegetarian() 几个方法外,我们新添加 print()、add()、remove() 和 setChild() 几个方法。后面新添加的方法对于菜单项没有意义,因为它是叶子节点,下面并没有任何组件,而菜单组件要包含其他的叶子节点,所以就需要这些方法。现在我们来看看菜单组件代码:

第一个抽象类 MenuComponent,他说菜单组件的抽象类,为叶子节点和组合节点提供一个共同的接口。我们在这个类中为这些方法提供默认的实现,这样不需要某些方法的类,比如叶子节点就可以不实现这些方法。

public abstract class MenuComponent {public void add(MenuComponent menuComponent) {throw new UnsupportedOperationException();}public void remove(MenuComponent menuComponent) {throw new UnsupportedOperationException();}public MenuComponent getChild(int i) {throw new UnsupportedOperationException();}public String getName() {throw new UnsupportedOperationException();}public String getDescription() {throw new UnsupportedOperationException();}public double getPrice() {throw new UnsupportedOperationException();}public boolean isVegetarian() {throw new UnsupportedOperationException();}public void print() {throw new UnsupportedOperationException();}
}

接着我们继续实现来看菜单项类,它是叶子节点,它实现组合内元素的行为。需要继承上面的 MenuComponent 抽象类。

public class MenuItem extends MenuComponent {String name;String description;boolean vegetarian;double price;public MenuItem(String name, String description, boolean vegetarian, double price) {this.name = name;this.description = description;this.vegetarian = vegetarian;this.price = price;}public String getName() {return name;}public String getDescription() {return description;}public boolean isVegetarian() {return vegetarian;}public double getPrice() {return price;}public void print() {System.out.print(" " + getName());if (isVegetarian()) {System.out.print("(v)");}System.out.println(", " + getPrice());System.out.println("    -- " + getDescription());}
}

下面是组合菜单,我们已经有了菜单项,还需要组合类,同样需要继承 MenuComponent 抽象类,重写一些方法。

public class NewMenu extends MenuComponent {ArrayList menuComponents = new ArrayList();String name;String description;public NewMenu(String name, String description) {this.name = name;this.description = description;}@Overridepublic void add(MenuComponent menuComponent) {menuComponents.add(menuComponent);}@Overridepublic void remove(MenuComponent menuComponent) {menuComponents.remove(menuComponent);}@Overridepublic MenuComponent getChild(int i) {return (MenuComponent) menuComponents.get(i);}@Overridepublic String getName() {return name;}@Overridepublic String getDescription() {return description;}@Overridepublic void print() {System.out.print("\n" + getName());System.out.println(", " + getDescription());System.out.println("-------------------------");Iterator iterator = menuComponents.iterator();while (iterator.hasNext()) {MenuComponent menuComponent = (MenuComponent) iterator.next();menuComponent.print();}}
}

这个类由于是组合类,所以需要一个数据集合,也就是它的孩子们,我们使用 ArrayList 来存储,重写一些方法,需要注意的是 print() 方法,我们可以使用迭代器,来调用集合中元素的 print() 方法,遇到叶子节点就直接打印,遇到菜单就继续遍历它的集合属性。

现在我们修改女侍者类,添加一个菜单节点的属性,添加一个构造方法,再修改 printMenu() 方法,调用的是它内在的菜单节点的 print() 方法。

public class Waitress {MenuComponent allMenus;public Waitress(MenuComponent allMenus) {this.allMenus = allMenus;}public void printMenu() {allMenus.print();}
}

现在我们开始测试。

 public static void main(String[] args) {NewMenu pancakeHouseMenu = new NewMenu("PANCAKE HOUSE MENU", "Breakfast");NewMenu dinerMenu = new NewMenu("DINER MENU", "Lunch");NewMenu cafeMenu = new NewMenu("CAFE MENU", "Dinner");NewMenu dessertMenu = new NewMenu("DESSERT MENU", "Dessert of course!");NewMenu allMenus = new NewMenu("ALL MENUS", "All menus combined");allMenus.add(pancakeHouseMenu);allMenus.add(dinerMenu);allMenus.add(cafeMenu);pancakeHouseMenu.add(new MenuItem("Veggie Burger and Air Fries","Veggie burger on a whole wheat bun, lettuce, tomato, and fries",true,3.99));dinerMenu.add(new MenuItem("Pasta","Spaghetti with Marinara Sauce, and a slice of sourdough bread",true,3.89));dinerMenu.add(dessertMenu);dessertMenu.add(new MenuItem("Apple Pie","Apple pie with a flakey crust, topped with vanilla ice cream",true,1.59));Waitress waitress = new Waitress(allMenus);waitress.printMenu();}

女招待的代码越来越简单,现在我们只需要在创建女侍者对象时传入一个菜单根节点,也就是最顶层的菜单组件交给她,我们就可以通过 printMenu() 方法打印菜单的具体功能。

我们的测试方法中,先创建了所有的菜单组件对象,然后创建的是要交给女侍者的 allMenus 对象,把每个菜单对象都添加到 allMenus 中,然后再在菜单对象中添加一些菜单项,最后创建女侍者对象,调用 printMenu() 方法打印菜单。

在整个系统中,组合模式以单一责任设计原则换取透明性,通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合与叶节点一视同仁。

前面我们也提过,女招待还可能提供菜单中的素食项,这时候就需要使用迭代器。我们先创建一个组合迭代器。

public class CompositeIterator implements Iterator {Stack stack = new Stack();public CompositeIterator(Iterator iterator) {stack.push(iterator);}@Overridepublic boolean hasNext() {if (stack.empty()) {return false;} else {Iterator iterator = (Iterator) stack.peek();if (!iterator.hasNext()) {stack.pop();return hasNext();} else {return true;}}}@Overridepublic Object next() {if (hasNext()) {Iterator iterator = (Iterator) stack.peek();MenuComponent component = (MenuComponent) iterator.next();stack.push(component.createIterator());return component;} else {return null;}}
}

通过一个栈存储迭代器。然后修改 MenuComponent 类,新增一个 createIterator() 方法,并修改子类的实现。

// MenuComponent.javapublic Iterator createIterator() {throw new UnsupportedOperationException();}// NewMenu.javaIterator<MenuComponent> iterator = null;public Iterator createIterator() {if (iterator == null) {iterator = new CompositeIterator(menuComponents.iterator());}return iterator;    }// MenuItem.javapublic Iterator createIterator() {return new NullIterator();}// NullIterator.java
public class NullIterator implements Iterator {@Overridepublic boolean hasNext() {return false;}@Overridepublic Object next() {return null;}public void remove() {throw new UnsupportedOperationException();}
}

菜单返回的是 CompositeIterator 对象,而菜单项返回的是空的迭代器。因为空迭代器的 hasNext() 方法,所以就不进行迭代。之所以不返回 null,就是因为不需要客户代码中进行一个返回值的判断,省去了一个步骤。

女侍者的素食打印菜单代码。

    public void printVegetarianMenu() {Iterator iterator = allMenus.createIterator();System.out.println("\nVEGETARIAN MENU\n----");while (iterator.hasNext()) {MenuComponent menuComponent = (MenuComponent)iterator.next();try {if (menuComponent.isVegetarian()) {menuComponent.print();}} catch (UnsupportedOperationException e) {}}}

这个方法先是获取到 allMenus 的迭代器,然后遍历每个元素,调用元素的 isVegetarian() 方法,如果为 true,就调用它的 print() 方法。只有菜单项的 print() 方法可以被调用,因为菜单的 isVegetarian() 方法是一直抛出异常的,所以不做任何操作,继续遍历。

到这里组合模式与适配器模式结合使用结束。

《Head First 设计模式》读书笔记——组合模式相关推荐

  1. 设计模式读书笔记-----模板方法模式

    首先我们先来看两个例子:冲咖啡和泡茶.冲咖啡和泡茶的基本流程如下: 所以用代码来创建如下: 咖啡:Caffee.java public class Coffee {void prepareRecipe ...

  2. Head First设计模式读书笔记——策略模式

    问题描述: 目前的任务是实现一个FPS类游戏的各种角色(友军.敌军.平民和狗.猫.鸭子等动物)以及他们的各种行为(攻击.游泳等). 设计方案一 很简单,只要实现一个角色超类,将角色的各种行为放入超类中 ...

  3. 设计模式读书笔记-单件模式

    单件模式- 确保一个类只有一个实例,全局只有一个入口点. 类如下: public class Singleton { private static Singleton uniqueInstance; ...

  4. 设计模式读书笔记-----代理模式

    在我们实际生活中代理情况无处不在!你在淘宝上面买东西,你使用支付宝平台支付,卖家请物流公司发货.你请朋友帮你拿包裹,在这个过程汇总支付宝.物流公司.你朋友都扮演者"第三者"的角色在 ...

  5. 设计模式读书笔记-----备忘录模式

    个人比较喜欢玩单机游戏,什么仙剑.古剑.鬼泣.使命召唤.三国无双等等一系列的游戏我都玩过(现在期待凡人修仙传),对于这些游戏除了剧情好.场面大.爽快之外,还可以随时存档,等到下次想玩了又可以从刚开始的 ...

  6. 设计模式学习笔记——组合(Composite)模式

    设计模式学习笔记--组合(Composite)模式 @(设计模式)[设计模式, 组合模式, composite] 设计模式学习笔记组合Composite模式 基本介绍 组合案例 类图 实现代码 Ent ...

  7. 设计模式读书笔记-----工厂方法模式

    一.问题 在前一章<设计模式读书笔记-----简单工厂模式>中通过披萨的实例介绍了简单工厂模式.在披萨实例中,如果我想根据地域的不同生产出不同口味的披萨,如纽约口味披萨,芝加哥口味披萨.如 ...

  8. 大话设计模式读书笔记

    主题 概要 设计模式 大话设计模式读书笔记 编辑 时间 新建 20170423 序号 参考资料 1 大话设计模式 重新看了一遍设计模式,除了一些已经特别熟悉的模式,都自己敲了一遍代码,有些豁然开朗的感 ...

  9. JavaScript设计模式读书笔记(一)= 创建型设计模式

    全系列目录 JavaScript设计模式读书笔记(一)=> 创建型设计模式 JavaScript设计模式读书笔记(二)=> 结构型设计模式 JavaScript设计模式读书笔记(三)=&g ...

最新文章

  1. Shell 十三问 的学习记录
  2. 博士因论文致谢走红后,回到母校演讲再刷屏!网友:是对寒门学子最好的激励...
  3. Severstal: Steel Defect Detection比赛的discussion调研
  4. 安卓开发设置全屏隐藏标题栏
  5. HDOJ 2030-汉字统计
  6. SpanBERT: 抽取式问答的利器
  7. POJ2115 C Looooops 扩展欧几里德
  8. alwayson09-创建always on高可用性组
  9. 微软MDT 安装与配置(一)
  10. 测试工程师必备技能之缺陷分析
  11. C++线索二叉树(中序线索二叉树及遍历)
  12. 反病毒引擎设计(一):绪论 本文来自:剑盟反病毒技术门户(www.janmeng.com)
  13. C# 滑块/滑杆/拖动条控件trackBar
  14. 大学生上课为什么一定要认真听讲?
  15. IntelliJ IDEA 2018.3 安装、永久破解及新特性说明
  16. 被巨头、快递、新贵分食的跨境电商
  17. 纯前端,js导出页面为pdf
  18. linux vsftp查看ftp账号信息的方法
  19. CL3D: Camera-LiDAR 3D Object Detection With Point Feature Enhancement and Point-Guided Fusion 阅读笔记
  20. HTML中多个radio只能选择一个、默认选中

热门文章

  1. Stata:系数为何不显著?GIF 演示 OLS 的性质
  2. iOS客户端公共WIFI解决方案
  3. SpringBoot自定义错误码,并支持国际化
  4. Unity | VS2019中代码颜色的更改
  5. 【Pandas】筛选某列过滤
  6. android手机短信转发+隐藏应用程序图片+开机自启
  7. VSCode 安装与配置
  8. 【文件包含漏洞】——文件包含漏洞进阶_日志文件包含利用
  9. Hadoop分布式高可用HA集群搭建笔记(含Hive之构建),java高级架构师视频
  10. 计算广告2——认识商业化体系