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

听起来可能有点抽象,我们在上一篇文章中迭代器模式的例子的基础上继续深入,来看看组合模式的奥妙。

在上一篇文章中,我们实现了西餐厅菜单、煎饼屋菜单和咖啡厅菜单,现在我们希望能够在西餐厅菜单上加上一份餐后甜点的“子菜单”。

这样一来我们又要修改我们的设计了,我们现在需要某种树形结构,可以容纳菜单、子菜单和菜单项。 我们需要确定能够在每个菜单的各个项之间游走,而且至少要像现在用迭代器一样方便。 我们也需要能够更有弹性地在菜单项之间游走。比方说,可能只需要遍历甜点菜单,或者可以遍历餐厅的整个菜单(包括甜点菜单在内)。

下面我们来看如何在菜单上应用组合模式。一开始,我们需要创建一个组件接口来作为菜单和菜单项的共同接口,让我们能够用统一的做法来处理菜单和菜单项。换句话说,我们可以针对菜单或菜单项调用相同的方法。菜单组件的角色是为叶节点和组合节点提供一个共同的接口。

组合模式的类图如下:

组件接口如下

import java.util.*;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 abstract Iterator createIterator();public void print() {throw new UnsupportedOperationException();}
}

现在我们来看菜单项类,这是组合类图里的叶类,它实现组合内元素的行为。

import java.util.Iterator;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 double getPrice() {return price;}public boolean isVegetarian() {return vegetarian;}public Iterator createIterator() {return new NullIterator();}public void print() {System.out.print(" " + getName());if (isVegetarian()) {System.out.print("(v)");}System.out.println(", " + getPrice());System.out.println(" -- " + getDescription());}
}

我们已经有了菜单项,还需要组合类,即菜单。组合类可以持有菜单项或其他菜单。

import java.util.Iterator;
import java.util.ArrayList;public class Menu extends MenuComponent {ArrayList menuComponents = new ArrayList();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 (MenuComponent)menuComponents.get(i);}public String getName() {return name;}public String getDescription() {return description;}public Iterator createIterator() {return new CompositeIterator(menuComponents.iterator());}public 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();}}
}

我们还实现了一个组合迭代器,用来遍历组件内的菜单项,而且确保所有的子菜单(以及子子菜单……)都被包括进来。

import java.util.*;public class CompositeIterator implements Iterator {Stack stack = new Stack();public CompositeIterator(Iterator iterator) {//将要遍历的顶层组合的迭代器传入,把它抛进一个堆栈数据结构中stack.push(iterator);}public Object next() {if(hasNext()) {Iterator iterator = (Iterator) stack.peek();MenuComponent menuComponent = (MenuComponent) iterator.next();if(menuComponent instanceof Menu) {//如果元素是一个菜单,则有了另一个需要被包含进遍历中的组合,把它也压入栈中stack.push(menuComponent.createIterator());}return menuComponent;}else {return null;}}public boolean hasNext() {if(stack.isEmpty()) {return false;}else {if(((Iterator)stack.peek()).hasNext()) {return true;}else {stack.pop();return hasNext();}}}public void remove() {throw new UnsupportedOperationException();}
}

我们还需要实现一个空迭代器,当菜单项内没有东西可以遍历时,就返回这个空迭代器,它的hasNext()永远返回false

import java.util.Iterator;public class NullIterator implements Iterator {public Object next() {return null;}public boolean hasNext() {return false;}public void remove() {throw new UnsupportedOperationException();}
}

现在我们可以来实现女招待了,并且为她加上了一个可以确切地告诉我们哪些项目是素食的方法。

import java.util.Iterator;public class Waitress {MenuComponent allMenus;public Waitress(MenuComponent allMenus) {this.allMenus = allMenus;}public void printMenu() {allMenus.print();}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) {}}}
}

最后来测试一下

public class MenuTestDrive {public static void main(String args[]) {MenuComponent pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "Breakfast");MenuComponent dinerMenu = new Menu("DINER MENU", "Lunch");MenuComponent cafeMenu = new Menu("CAFE MENU", "Dinner");MenuComponent dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");allMenus.add(pancakeHouseMenu);allMenus.add(dinerMenu);allMenus.add(cafeMenu);pancakeHouseMenu.add(new MenuItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true,2.99));pancakeHouseMenu.add(new MenuItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false,2.99));pancakeHouseMenu.add(new MenuItem("Blueberry Pancakes","Pancakes made with fresh blueberries, and blueberry syrup",true,3.49));pancakeHouseMenu.add(new MenuItem("Waffles","Waffles, with your choice of blueberries or strawberries",true,3.59));dinerMenu.add(new MenuItem("Vegetarian BLT","(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99));dinerMenu.add(new MenuItem("BLT","Bacon with lettuce & tomato on whole wheat", false, 2.99));dinerMenu.add(new MenuItem("Soup of the day","A bowl of the soup of the day, with a side of potato salad", false, 3.29));dinerMenu.add(new MenuItem("Hotdog","A hot dog, with saurkraut, relish, onions, topped with cheese",false, 3.05));dinerMenu.add(new MenuItem("Steamed Veggies and Brown Rice","A medly of steamed vegetables over brown rice", 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 icecream",true,1.59));dessertMenu.add(new MenuItem("Cheesecake","Creamy New York cheesecake, with a chocolate graham crust",true,1.99));dessertMenu.add(new MenuItem("Sorbet","A scoop of raspberry and a scoop of lime",true,1.89));cafeMenu.add(new MenuItem("Veggie Burger and Air Fries","Veggie burger on a whole wheat bun, lettuce, tomato, and fries",true, 3.99));cafeMenu.add(new MenuItem("Soup of the day","A cup of the soup of the day, with a side salad",false, 3.69));cafeMenu.add(new MenuItem("Burrito","A large burrito, with whole pinto beans, salsa, guacamole",true, 4.29));Waitress waitress = new Waitress(allMenus);waitress.printVegetarianMenu();}
}

下面是运行结果

设计分析

值得注意的是,组合模式不符合我们在先前的文章中所介绍的单一责任制度。组合模式让一个类有两个责任,不但需要管理层次结构,还要执行菜单的操作。可以这么说,组合模式以单一责任设计原则换取透明性(transparency)。所谓透明性,即是通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的

设计模式(11)——组合模式相关推荐

  1. 软件设计模式与体系结构实验——3.1-1组合模式的应用

    链接: 软件设计模式与体系结构实验--2.1-1(2)(抽象)工厂模式的应用 链接: 软件设计模式与体系结构实验--2.2-1生成器模式的应用 链接: 软件设计模式与体系结构实验--2.3-1单列模式 ...

  2. 1、【设计模式】组合模式

    java设计模式之组合模式 [学习难度:★★★☆☆,使用频率:★★★★☆]  树形结构在软件中随处可见,例如操作系统中的目录结构.应用软件中的菜单.办公系统中的公司组织结构等等,如何运用面向对象的方式 ...

  3. java设计模式之组合模式(树形层级)

    java设计模式之组合模式 学习难度:★★★☆☆,使用频率:★★★★☆]  树形结构在软件中随处可见,例如操作系统中的目录结构.应用软件中的菜单.办公系统中的公司组织结构等等,如何运用面向对象的方式来 ...

  4. 如何让孩子爱上设计模式 ——11.外观模式(Facade Pattern)

    如何让孩子爱上设计模式 --11.外观模式(Facade Pattern) 标签: 设计模式初涉 场景引入 相信各位玩过LOL英雄联盟游戏的童鞋,对下面两个英雄都不会陌生吧:       分别是瑞雯和 ...

  5. 每天一个设计模式之组合模式

    作者按:<每天一个设计模式>旨在初步领会设计模式的精髓,目前采用javascript和python两种语言实现.诚然,每种设计模式都有多种实现方式,但此小册只记录最直截了当的实现方式 :) ...

  6. 详解设计模式:组合模式

    组合模式(Composite Pattern),又叫部分整体模式,是 GoF 的 23 种设计模式中的一种结构型设计模式. 组合模式 是用于把一组相似的对象当作一个单一的对象.组合模式依据树形结构来组 ...

  7. 结构型设计模式之组合模式

    结构型设计模式之组合模式 组合模式 应用场景 优缺点 主要角色 组合模式结构 分类 透明组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 安全组合模式 创建抽象根节点 创建树枝节点 创 ...

  8. Java设计模式之组合模式详解

    文章目录 详解Java设计模式之组合模式 案例引入 组合模式 定义 模式类图结构 相关角色 典型代码 案例分析 类图设计 实例代码 结果分析 JavaJDK中的组合模式 透明组合模式 安全组合模式 组 ...

  9. Java设计模式之组合模式(UML类图分析+代码详解)

    大家好,我是一名在算法之路上不断前进的小小程序猿!体会算法之美,领悟算法的智慧~ 希望各位博友走过路过可以给我点个免费的赞,你们的支持是我不断前进的动力!! 加油吧!未来可期!! 本文将介绍java设 ...

  10. 设计模式之组合模式(Composite)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

最新文章

  1. 【js实例】Array类型的9个数组方法,Date类型的41个日期方法,Function类型
  2. Python:eval的妙用和滥用
  3. sql显示前10行数据_SPL 简化 SQL 案例详解:计算各组前 N 行
  4. C/C++知识分享:C++常用内置函数你会几个,使用过几次呢?
  5. linux下安装mysql数据库
  6. 微软亚马逊联手发布深度学习库Gluon,适合所有段位的炼丹师
  7. JSK-399 绝对值最小的数【大数】
  8. 解析深度学习:语音识别实践 (俞栋等著) 完整pdf[44MB]
  9. Android框架揭秘-Zygote笔记
  10. zigbee协议栈学习(0)
  11. 基于RTMP协议的音视频传输----FLV格式
  12. 数据寻址——偏移寻址
  13. qq大厅连连看外挂:c++实现
  14. 数据治理:数据质量管理办法
  15. 记录docker failed to initialize docker desktop is shutting down解决办法
  16. List、Collections
  17. 免息贷款但有手续费的年化利率计算方法及Java实现;
  18. 微博首席架构师杨卫华:新浪微博技术架构分析和InfoQ访谈
  19. 为何要从用户角度出发来思考问题
  20. 常见移动机器人运动学模型总结

热门文章

  1. 39. PHP 错误与异常处理(3)
  2. 11. CSS 文本属性
  3. onchange与oninput的区别
  4. jQuery 提供了多种遍历 DOM 的方法。 遍历方法中最大的种类是树遍历(tree-traversal)。jQuery 提供了多种遍历 DOM 的方法。 遍历方法中最大的种类是树遍历(tree-t
  5. 应用安全-安全设备-Waf系列-软Waf-云锁
  6. 4~20mA变送器量程与输入电流、输出电流的关系
  7. 2019php面试题
  8. ubuntu gedit 工具菜单下没有 Manage external tools
  9. 安装apache-2.2.6, php-5.2.5, mysql-5.0.2的过程
  10. Kafka配置1--Windows环境安装和配置Kafka