《Head First设计模式》第九章(2)组合模式_menucomponent pancakehousemenu

组合模式

​ 基于前一篇迭代模式的案例进行需求更新,餐厅的菜单管理系统需要有煎饼屋菜单和披萨菜单。现在希望在披萨菜单中能够加上一份餐后甜点的子菜单。
在迭代模式中,披萨菜单是用数组维护的,我们需要让披萨菜单持有一份子菜单,但是不能真的把他赋值给菜单项数组,因为类型不同,所以不能这么做。
所以,需要重新实现煎饼屋菜单和披萨菜单了。事实是,我们已经到达了一个复杂级别,如果现在不重新设计,就无法容纳未来增加的菜单或子菜单的需求。我们需要一下改变:

  • 需要某种树形结构,可以容纳菜单、子菜单和菜单项;
  • 需要确定能够在每个菜单的各个项之间游走,而且至少像用迭代器一样方便;
  • 需要能够更有弹性地在菜单项之间游走。比方说,可能只需要遍历甜点菜单,或者可以便利整个菜单;

我们首先想到的是采用树形结构:

组合模式树形结构

​ 我们要使用组合模式来解决这个问题,但并没有放弃迭代器模式,它仍然是解决方案中的一部分,然而管理菜单的问题已经到了一个迭代器无法解决的新维度。所以,我们将倒退几步,使用组合模式来解决。

​ 组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。使用组合结构,我们能把相同的操作应用在组合的个别对象上,换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。

定义

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

组合模式能创建一个树形结构

图片说明

​ 图片说明

我们要如何将组合模式利用在菜单上呢?一开始,我们需要创建一个组件接口来作为菜单和菜单项的共同接口,让我们能够用同意的做法来处理菜单和菜单项。来看看设计的类图:

图片说明

​ 菜单组件MenuComponent提供了一个接口,让菜单项和菜单共同使用。因为我们希望能够为这些方法提供默认的实现,所以我们在这里可以把MenuComponent接口换成一个抽象类。在这个类中,有显示菜单信息的方法getName()等,还有操纵组件的方法add(),remove(),getChild()等。

​ 菜单项MenuItem覆盖了显示菜单信息的方法,而菜单Menu覆盖了一些对他有意义的方法。

​ 具体来看看代码实现:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159public abstract class MenuComponent {   // add,remove,getchild // 把组合方法组织在一起,即新增、删除和取得菜单组件   public void add(MenuComponent component) { throw new UnsupportedOperationException(); }   public void remove(MenuComponent component) { 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(); } }   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.println(``" " + getName()); if (isVegetarian()) { System.out.println(``"(V)"``); } System.out.println(``", " + getPrice()); System.out.println(``" -- " + getDescription()); } }   public class Menu``extends MenuComponent { ArrayList<MenuComponent> menuComponents =``new ArrayList<MenuComponent>(); String name; String description;   public Menu(String name, String description) { this``.name = name; this``.description = description; }   public void add(MenuComponent menuComponent) { menuComponents.add(menuComponent); }   public void remove(MenuComponent menuComponent) { menuComponents.remove(menuComponent); }   public MenuComponent getChild(``int i) { return menuComponents.get(i); }   public String getName() { return name; }   public String getDescription() { return description; }   public void print() { System.out.println(``"\n" + getName()); System.out.println(``", " + getDescription()); System.out.println(``"----------------------"``);   Iterator<MenuComponent> iterator = menuComponents.iterator(); while``(iterator.hasNext()) { MenuComponent menuComponent = iterator.next(); menuComponent.print(); } } }     public class Waitress { MenuComponent allMenus;   public Waitress(MenuComponent allMenus) { this``.allMenus = allMenus; }   public void printMenu() { allMenus.print(); } }     public class Client {   public static void main(String[] args) { // 创建菜单对象 MenuComponent pancakeHouseMenu =``new Menu(``"煎饼屋菜单"``,``"提供各种煎饼。"``); MenuComponent pizzaHouseMenu =``new Menu(``"披萨屋菜单"``,``"提供各种披萨。"``); MenuComponent cafeMenu =``new Menu(``"咖啡屋菜单"``,``"提供各种咖啡"``); // 创建一个顶层的菜单 MenuComponent allMenus =``new Menu(``"All Menus"``,``"All menus combined"``); // 把所有菜单都添加到顶层菜单 allMenus.add(pancakeHouseMenu); allMenus.add(pizzaHouseMenu); allMenus.add(cafeMenu); // 在这里加入菜单项 pancakeHouseMenu.add(``new MenuItem(``"苹果煎饼"``,``"香甜苹果煎饼"``,``true``,``5.99``)); pizzaHouseMenu.add(``new MenuItem(``"至尊披萨"``,``"意大利至尊咖啡"``,``false``,``12.89``)); cafeMenu.add(``new MenuItem(``"美式咖啡"``,``"香浓美式咖啡"``,``true``,``3.89``));   Waitress waitress =``new Waitress(allMenus); waitress.printMenu(); }   }

程序输出结果

​ 组合模式以单一责任设计原则换取透明性。通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。

​ 现在,我们在MenuComponent类中同时具有两种类型的操作。因为客户有机会对一个元素做一些不恰当或是没有意义的操作,所以我们失去了一些安全性。

扩展:组合迭代器

我们现在再扩展一下,这种组合菜单如何设计迭代器呢?细心的朋友应该观察到,我们刚才使用的迭代都是递归调用的菜单项和菜单内部迭代的方式。现在我们想设计一个外部迭代的方式怎么办?譬如出现一个新需求:服务员需要打印出蔬菜性质的所有食品菜单。首先,我们给MenuComponent加上判断蔬菜类食品的方法,然后在菜单项中进行重写:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30public abstract class MenuComponent {   ………… /** * 判断是否为蔬菜类食品 */ public boolean isVegetarian() { throw new UnsupportedOperationException(); } } /** * 菜单项 */ public class MenuItem``extends MenuComponent{ String name; double price; /**蔬菜类食品标志*/ boolean vegetarian;   …………   public boolean isVegetarian() { return vegetarian; }   public void setVegetarian(``boolean vegetarian) { this``.vegetarian = vegetarian; }   }

这个CmpositeIterator是一个不可小觑的迭代器,它的工作是遍历组件内的菜单项,而且确保所有的子菜单(以及子子菜单……)都被包括进来。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56//跟所有的迭代器一样,我们实现Iterator接口。 class CompositeIterator``implements Iterator { Stack stack =``new Stack(); /** *将我们要遍历的顶层组合的迭代器传入,我们把它抛进一个堆栈数据结构中 */ public CompositeIterator(Iterator iterator) { stack.push(iterator); }   @Override public boolean hasNext() { //想要知道是否还有下一个元素,我们检查堆栈是否被清空,如果已经空了,就表示没有下一个元素了 if (stack.empty()) { return false``; }``else { /** *否则我们就从堆栈的顶层中取出迭代器,看看是否还有下一个元素, *如果它没有元素,我们将它弹出堆栈,然后递归调用hasNext()。 */ Iterator iterator = (Iterator) stack.peek(); if (!iterator.hasNext()) { stack.pop(); return hasNext(); }``else { //否则,便是还有下一个元素 return true``; } } }   @Override public Object next() { //好了,当客户想要取得下一个元素时候,我们先调用hasNext()来确定时候还有下一个。 if (hasNext()) { //如果还有下一个元素,我们就从堆栈中取出目前的迭代器,然后取得它的下一个元素 Iterator iterator = (Iterator) stack.peek(); MenuComponent component = (MenuComponent) iterator.next(); /** *如果元素是一个菜单,我们有了另一个需要被包含进遍历中的组合, *所以我们将它丢进对战中,不管是不是菜单,我们都返回该组件。 */ if (component``instanceof Menu) { stack.push(component.createIterator()); } return component; }``else { return null``; } }   @Override public void remove() { throw  new UnsupportedOperationException(); } }

在我们写MenuComponent类的print方法的时候,我们利用了一个迭代器遍历组件内的每个项,如果遇到的是菜单,我们就会递归地电泳print方法处理它,换句话说,MenuComponent是在“内部”自行处理遍历。
但是在上页的代码中,我们实现的是一个“外部”的迭代器,所以有许多需要追踪的事情。外部迭代器必须维护它在遍历中的位置,以便外部可和可以通过hasNext和next来驱动遍历。在这个例子中,我们的代码也必须维护组合递归结构的位置,这也就是为什么当我们在组合层次结构中上上下下时,使用堆栈来维护我们的位置。

空迭代器

菜单项没什么可以遍历的,那么我们要如何实现菜单项的createIterator()方法呢。
1:返回null。我们可以让createIterator()方法返回null,但是如果这么做,我们的客户代码就需要条件语句来判断返回值是否为null;
2:返回一个迭代器,而这个迭代器的hasNext()永远返回false。这个是更好的方案,客户不用再担心返回值是否为null。我们等于创建了一个迭代器,其作用是“没作用”。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17class NullIterator``implements Iterator{   @Override public boolean hasNext() { return false``; }   @Override public Object next() { return null``; }   @Override public void remove() { throw  new UnsupportedOperationException(); } }

​ 以上便是组合模式的一些内容。

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里无偿获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值