之前的总结链接:
https://blog.csdn.net/u011109881/article/details/58710579
对比headFirst书中的例子,我觉得书中的组合模式的例子比上面的例子更好一些。上面的例子虽然通俗易懂,但是总感觉不够深入,也不够完善。

组合模式+迭代器模式

接着上一节最后的例子,例子的最终list结构图是这样的:

若要给DinerMenu新加一种Menu(即下面这样),则需要对现有结构进行较大改动。


可以看到,目前的结构中分为两种结构,一种是menu,是一种容器,可以包含菜单项,而第二种是menuItem,是一个节点,只包含该菜单项的相关信息。
如果把这种关系推广到树形结构,那么menu则相当于非叶子节点,而menu则相当于叶子节点。从上图也很容易看出来。
运用组合模式,则可以实现上面的这种树形结构,并且方便我们的遍历。

组合模式示例

示例基于上一节最后的代码改进
以MenuComponent为基类

public abstract class MenuComponent {//適用于菜單項+菜單容器public String getName() {throw new UnsupportedOperationException();}//適用于菜單項+菜單容器public String getDescription() {throw new UnsupportedOperationException();}//適用于菜單項public boolean isVegetarian() {throw new UnsupportedOperationException();}//適用于菜單項public double getPrice() {throw new UnsupportedOperationException();}//適用于菜單容器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 void print() {throw new UnsupportedOperationException();}
}

menu和menuItem继承之

public class Menu extends MenuComponent {String name;String description;ArrayList<MenuComponent> menuItems;public Menu(String name, String description) {menuItems = new ArrayList<MenuComponent>();this.name = name;this.description = description;}@Overridepublic void add(MenuComponent menuComponent) {menuItems.add(menuComponent);}@Overridepublic void remove(MenuComponent menuComponent) {menuItems.remove(menuComponent);}@Overridepublic MenuComponent getChild(int i) {return menuItems.get(i);}@Overridepublic String getName() {return this.name;}@Overridepublic String getDescription() {return this.description;}@Overridepublic void print() {System.out.println(" " + getName());System.out.println(" "  + getDescription());System.out.println(" ------------------------------------ ");Iterator<MenuComponent> iterator = menuItems.iterator();//重点 递归调用while (iterator.hasNext()) {MenuComponent menuComponent = iterator.next();menuComponent.print();}}}

关键在于这里的递归,利用iterator 遍历Menu中的子项,注意这些子项既可能是Menu也可能时MenuItem。如果时MenuItem,则调用的是MenuItem的print方法,如果是Menu,则会调用Menu的print,此时则会进入菜单的第一次递归调用。这里其实运用到多态的思想。

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

改进Waitress

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

DinerMenu和PancakeHouseMenu没有存在必要了,改写测试类

public class Printer2 {public static void main(String[] args) {MenuComponent breakfasetMenu = new Menu("PANCAKE HOUSE MENU", "breakfast");MenuComponent lunchMenu = new Menu("LUNCH MENU", "lunch");MenuComponent dinerMenu = new Menu("DINER MENU", "diner");MenuComponent allMenuComponent = new Menu("ALL MENUS","All menus combined");allMenuComponent.add(dinerMenu);allMenuComponent.add(lunchMenu);allMenuComponent.add(breakfasetMenu);dinerMenu.add(new MenuItem("diner menu item1", "dinermenu item1", true, 1.23));dinerMenu.add(new MenuItem("diner menu item2", "dinermenu item2", false, 1.43));dinerMenu.add(new MenuItem("diner menu item3", "dinermenu item3", true, 3.23));lunchMenu.add(new MenuItem("lunchMenu menu item1", "lunchMenu item1", false, 1.434));lunchMenu.add(new MenuItem("lunchMenu menu item2", "lunchMenu item2", true, 6.5));breakfasetMenu.add(new MenuItem("breakfasetMenu menu item1", "breakfasetMenu item1", true, 1.2));breakfasetMenu.add(new MenuItem("breakfasetMenu menu item2", "breakfasetMenu item2", false, 1.12));Waitress waitress = new Waitress(allMenuComponent);waitress.printMenu();}}

测试结果:

 ALL MENUSAll menus combined------------------------------------ DINER MENUdiner------------------------------------ diner menu item1
isVegetarian true1.23--dinermenu item1diner menu item2
isVegetarian false1.43--dinermenu item2diner menu item3
isVegetarian true3.23--dinermenu item3LUNCH MENUlunch------------------------------------ lunchMenu menu item1
isVegetarian false1.434--lunchMenu item1lunchMenu menu item2
isVegetarian true6.5--lunchMenu item2PANCAKE HOUSE MENUbreakfast------------------------------------ breakfasetMenu menu item1
isVegetarian true1.2--breakfasetMenu item1breakfasetMenu menu item2
isVegetarian false1.12--breakfasetMenu item2

完成时的类结构:

可以看到,在组合模式中,Menu和MenuItem都是一个个节点,都可以参与遍历。注意:Waitess其实一个Menu节点,为了与上面的例子结合起来看,我将他写成了Waitress,其实重点应该在Waitress中包含的allMenus实例。
组合模式的思想

关键点:继承同一个抽象类。
可以看到Menu和MenuItem都继承了抽象父类MenuComponent,这样做
可以忽略容器和叶子节点的节点的区别。至于为什么这么做,是方便菜单项和子项的统一遍历(不需要区分是Menu还是MenuItem----多态的功劳)
试想一下,如果只有Menu的话,遍历顺序是什么样的呢?从上面的输出结果,不难想象,这种遍历,与二叉树的先序遍历极为类似。
至于MenuItem则可以想象为不能拥有子节点的Menu。
以上总结为两点:
1.组合模式如何实现:容器(MENU)与节点(MENUITEM)继承同一个接口
2.组合模式目的:实现更简洁的遍历

等一下,MenuComponent似乎违背了单一职责原则?
通常,我们推荐一个类只干一件事,但是在MenuComponent似乎既干了菜单的事情也干了菜单项的事情,如何解释呢?
没错,组合模式的确违反了单一职责原则,但是这是有目的的。
组合模式通过这种方式让容器节点和叶子节点一视同仁的遍历和操作,用户不需要知道他操作的是容器节点还是叶子节点。
如果我们不采用这种方式,那么客户势必需要使用instanceof来判断、分别处理叶子和容器节点,这样透明性变差(用户需要知道节点的内部结构)。因此这里的操作是牺牲单一职责换取透明性(用户不需要知道他操作的是容器节点还是叶子节点)。
在日常开发中,类似的权衡处理方式是时间换空间或者空间换时间的算法,这样应该更容易理解吧?

组合模式终极版:组合(外部)迭代器

1.改进MenuComponent,添加新方法

 //新增方法 适用于菜单+菜单项public Iterator<MenuComponent> createIterator(){throw new UnsupportedOperationException();}

2.为Menu和MenuItem各自实现该方法
Menu:

 @Overridepublic Iterator<MenuComponent> createIterator() {return menuItems.iterator();}

MenuItem:

 @Overridepublic Iterator<MenuComponent> createIterator() {return new NullIterator();}

3.NullIterator:(空迭代器)

public class NullIterator implements Iterator<MenuComponent> {public boolean hasNext() {return false;}public MenuComponent next() {return null;}}

4.组合迭代器登场

public class CompositeIterator implements Iterator<MenuComponent> {Stack<Iterator<MenuComponent>> stack = new Stack<Iterator<MenuComponent>>();//存储iterator的栈public CompositeIterator(Iterator<MenuComponent> iterator) {stack.push(iterator);}public boolean hasNext() {if (stack.isEmpty()) {// if stack is null, does not have nextreturn false;} else {// get the top of stackIterator<MenuComponent> iterator = stack.peek();if (!iterator.hasNext()) {//如果栈顶iterator没有元素 弹出该iterator,递归判断是否hasNextif(iterator instanceof CompositeIterator){System.out.println("pop>>>>>>>>>>>>>>>>>>>"+((CompositeIterator)stack.pop()));}else{System.out.println("pop>>>>>>>>>>>>>>>>>>>"+iterator.getClass());stack.pop();}return hasNext();} else {//栈顶iterator有next元素,返回truereturn true;}}}public MenuComponent next() {System.out.println("xxxxx");//取出栈顶iteratorIterator<MenuComponent> iterator = stack.peek();//取出栈顶iterator的nextMenuComponent menuComponent = iterator.next();System.out.println("hasNEx");if (menuComponent instanceof Menu) {//如果是Menu类型 还需要放入栈内 为了后面的遍历System.out.println("<<<<<<<<<<<<<<<<<<<<<push"+menuComponent.getName()+" "+stack.push(menuComponent.createIterator()));}//不管是Menu还是MenuItem都返回return menuComponent;}}

5.当然侍者类也要改进

public class Waitress {MenuComponent allMenus;public Waitress(MenuComponent allMenus) {this.allMenus = allMenus;}public void printMenu() {allMenus.print();}public void printVegetarianMenu(){//得到allMenu的Iterator 其类型为CompositeIterator(所有Menu的createIterator返回的实际实例类型为CompositeIterator)//遍历之得到 存储在 allMenus的MenuComponentIterator<MenuComponent> iterator = allMenus.createIterator();System.out.println(" printVegetarianMenu  ");while(iterator.hasNext()){//调用的CompositeIterator的hasNextMenuComponent menuComponent =iterator.next();System.out.println("for next....");try{if(menuComponent.isVegetarian()){menuComponent.print();}}catch(UnsupportedOperationException e){System.err.println("Not a menuItem");}}}
}
/**
遍历过程:
1.对allMenus遍历 将breakfasetMenu和lunchMenu放入stack
2.对breakfasetMenu进行遍历,判断其子元素是否是蔬菜,遍历完毕,从stack弹出breakfasetMenu
3.对lunchMenu进行遍历,判断子元素是否是蔬菜,遇到dinerMenu时放入stack
4.对dinerMenu进行遍历,判断子元素是否是蔬菜,遍历完毕,弹出dinerMenu
5.对lunchMenu遍历完毕,弹出lunchMenu.
6.对allMenus遍历完毕,弹出allMenus,遍历终了*/

遍历图:

不过dinner貌似会被push两次,所以会输出两次。看来几遍没找到原因。。以后再看看吧。。
log:

 printVegetarianMenu
Not a menuItem
<<<<<<<<<<<<<<<<<<<<<pushPANCAKE HOUSE MENU iterator.CompositeIterator@15db9742
for next....
for next....====================breakfasetMenu menu item1
isVegetarian true1.2breakfasetMenu item1====================
for next....
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr
pop>>>>>>>>>>>>>>>>>>>iterator.CompositeIterator@15db9742
<<<<<<<<<<<<<<<<<<<<<pushLUNCH MENU iterator.CompositeIterator@6d06d69c
for next....
Not a menuItem
for next....
<<<<<<<<<<<<<<<<<<<<<pushDINER MENU iterator.CompositeIterator@7852e922
<<<<<<<<<<<<<<<<<<<<<pushDINER MENU iterator.CompositeIterator@4e25154f
for next....
Not a menuItem
for next....====================diner menu item1
isVegetarian true1.23dinermenu item1====================
for next....
for next....====================diner menu item3
isVegetarian true3.23dinermenu item3====================
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr
pop>>>>>>>>>>>>>>>>>>>iterator.CompositeIterator@4e25154f
for next....====================diner menu item1
isVegetarian true1.23dinermenu item1====================
for next....
for next....====================diner menu item3
isVegetarian true3.23dinermenu item3====================
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr
pop>>>>>>>>>>>>>>>>>>>iterator.CompositeIterator@7852e922
for next....====================lunchMenu menu item2
isVegetarian true6.5lunchMenu item2====================
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr
pop>>>>>>>>>>>>>>>>>>>iterator.CompositeIterator@6d06d69c
pop>>>>>>>>>>>>>>>>>>>class java.util.ArrayList$Itr

这个最后的迭代器稍显复杂,其目的是为了构建一个外部迭代器,但是个人觉得上一个内部迭代器完全够用了,毕竟对于大多数集合类,有已经支持增强for循环了,这个可用可以不用Iterator似乎没那么大作用了。至于这个push两次的问题,有时间再看看吧。。。

Head First设计模式读书笔记八 第九章下 组合模式相关推荐

  1. Head First设计模式读书笔记八 第九章上 迭代器模式

    之前的总结: https://blog.csdn.net/u011109881/article/details/59677544 个人觉得本章节,HeadFirst讲的没有之前看到的网站讲的清晰,至少 ...

  2. 《HeadFirst设计模式》读书笔记-第9章v2-组合模式

    定义 组合模式(composite pattern)允许你将对象组合成树形结构来表现"整体/部分"层次结构.组合能让客户以一致的方式处理个别对象以及对象组合. 代码实现 本章使用组 ...

  3. Think in Java第四版 读书笔记4第九章第十章

    第九章 抽象类与接口 9.1抽象类和抽象接口 抽象类可以有具体实现的方法(并不是所有方法都是abstract的)(比如这样 abstract void test3();) 子类继承抽象类要么要实现(覆 ...

  4. JS高级程序设计读书笔记 (第九章 客户端检测)

    第九章 客户端检测 能力检测 最常用也最为人们广泛接受的客户端检测形式是能力检测(又称特性检测).能力检测的目标不是识别特定的浏览器,而是识别浏览器的能力.采用这种方式不必顾及特定的浏览器如何如何,只 ...

  5. 《HeadFirst设计模式》第九章-2组合模式

    1.声明 设计模式中的设计思想.图片和部分代码参考自<Head First设计模式>,作者Eric Freeman & Elisabeth Freeman & Kathy ...

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

    Composite组合模式主要是应对这样的问题:一类具有"容器特征"的对象--即他们在充当对象的同时,又是其他对象的容器的情况.在编写时我们常常会造成:客户代码过多地依赖于对象容器 ...

  7. 《Head First设计模式》读书笔记 -- (第一章)策略模式

    本文属贫僧不吃肉原创,欢迎转载,转载请注明来自 http://never-say-never.iteye.com/blog/851923 在开始之前,先熟悉一下OO设计的一些原则. OO设计原则之一: ...

  8. 《大话设计模式》读书笔记-第8章 工厂方法模式

    1.工厂方法(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法是一个类的实例化延迟到其子类. 2.工厂方法(Factory Method)结构图 3.工厂 ...

  9. java2第九章的总结_java并发的艺术-读书笔记-第九章线程池

    使用线程池的好处: 1.降低资源消耗:减少了线程创建和销毁的资源消耗 2.提高响应速度,当任务到达时,线程可以不尽兴创建直接处理 3.提高线程的可管理性.使用线程池可以对线程进行统一的管理,监控,使用 ...

最新文章

  1. ZendStudio导入一个已有的网站
  2. BASH命令和SHELL脚本学习
  3. Table control中列隐藏实现方法
  4. asp是什么文件?html和asp的区别?(HTML是客户端语言,主要用于创建静态网页;asp是服务器端语言,用于设计用户交互式页面或动态页面)
  5. Dynamics CRM 访问团队的使用
  6. linux基础命令rpm,rpm常用命令集合1
  7. centos下如何使用sendmail发送邮件
  8. hive元数据为什么要用mysql_Hive用MySQL做元数据保存的数据库时,mySQL设置
  9. quartz和应用的集群问题
  10. python创建虚拟环境报错typeerror_python 创建虚拟环境时报错OSError, setuptools下载失败...
  11. 厉精图治的拼音及解释
  12. Rust 越来越香了!AWS 雇佣 Rust 编译器团队负责人 Felix Klock
  13. Http网络传递参数中文乱码问题解决办法
  14. android Xmpp+openfire 消息推送 :SASL authentication failed using mechanism DIGEST-MD5
  15. 我们该怎么把图片转文字呢?智能提取文字软件有哪些?
  16. 制造业ERP系统具体操作流程是什么?
  17. 全减器及其相关概念的理解
  18. centos7安装abaqus2020
  19. 分享几个软件测试逻辑思维和综合面试题
  20. 报错解决:symbol lookup error-----undefined symbol: JLI_StringDup

热门文章

  1. php 安装zip,php zip拓展安装
  2. Ionic4.x 中自定义公共模块
  3. asp.net core webapi Session 内存缓存
  4. commonJS — 数字操作(for Number)
  5. Entity Framework 6 Code First的简单使用和更新数据库结构
  6. 公司的故事之老板的平衡术
  7. java es 搜索_使用elasticsearch从多个列表中搜索
  8. android 底部停靠 底部吸附_android让xml布局的底部跟随软键盘
  9. 资源放送丨《MGR原理介绍与案例分享》PPT视频
  10. 直播预告丨 统一数据操作平台— CloudQuery 应用指南