组合模式

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

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

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

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

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

定义

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

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

​ 

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

​ 菜单组件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

159

public 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("苹果煎饼""香甜苹果煎饼"true5.99));

        pizzaHouseMenu.add(new MenuItem("至尊披萨""意大利至尊咖啡"false12.89));

        cafeMenu.add(new MenuItem("美式咖啡""香浓美式咖啡"true3.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

30

public 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

17

class NullIterator implements Iterator{

    @Override

    public boolean hasNext() {

        return false;

    }

    @Override

    public Object next() {

        return null;

    }

    @Override

    public void remove() {

        throw  new UnsupportedOperationException();

    }

}

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

《Head First设计模式》第九章(2)组合模式相关推荐

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

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

  2. Head First设计模式读书笔记八 第九章下 组合模式

    之前的总结链接: https://blog.csdn.net/u011109881/article/details/58710579 对比headFirst书中的例子,我觉得书中的组合模式的例子比上面 ...

  3. Java设计模式(8)组合模式(Composite模式)

    Composite定义:将对象以树形结构组织起来,以达成"部分-整体" 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性. Composite比较容易理解,想到Compo ...

  4. 设计模式十二之组合模式

    设计模式十二之组合模式 1. 模式的定义与特点 1.1 模式的定义 1.2 模式的特点 1.3 模式的使用场景 2. 模式的结构与实现 2.1 模式的结构 2.2 模式的实现 3. 模式在开源软件中的 ...

  5. 设计模式——(12)组合模式

    第十二章 组合模式 1.1 引言 餐厅都有自己的一份菜单,而菜单由菜单项组成,每个菜单项描述菜名.菜价和描述等信息. 而在一份菜单中,可能包含子菜单,例如:餐厅菜单除了包含菜名,还包含一份甜品子菜单. ...

  6. 设计模式的理解:组合模式 (Composite)

    组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象.组合模式依据树形结构来组合对象,用来表示部分以及整体层次.这种类型的设计模式属于结构型模式, ...

  7. 设计模式08: Composite 组合模式(结构型模式)

    Composite 组合模式(结构型模式) 对象容器的问题 在面向对象系统中,我们常会遇到一类具有"容器"特征的对象--即他们在充当对象的同时,又是其他对象的容器. public ...

  8. 大战设计模式【13】—— 组合模式

    组合模式(Composite) 设计模式使用的例子https://github.com/LinkinStars/DesignPatternsAllExample 一.定义 组合多个对象形成树形结构以表 ...

  9. 设计模式笔记九:组合模式

    原文:http://www.runoob.com/design-pattern/ 少许个人理解,如有错误请指出.欢迎一起讨论. 组合模式(Composite Pattern) 又叫部分整体模式,是用于 ...

  10. 设计模式之禅【组合模式】

    真刀实枪之组合模式 从公司的人事架构谈起吧 公司的组织架构 从上图中可以分析出: 有分支的节点(树枝节点) 无分支的节点(叶子节点) 根节点(无父节点) 有了树状结构图,再看看类图长啥样吧! 这个类图 ...

最新文章

  1. 重磅,武汉大学获捐10亿元!
  2. qt 串口粘包_Qt Socket 传输图片——图像拆包、组包、粘包处理
  3. Django登录验证——原生表单验证
  4. 计算机基础教程7 - 数字系统
  5. 1110 Complete Binary Tree (25 分)(搜索)
  6. python猜数字游戏
  7. 牛顿迭代公式(详细)
  8. P3373 【模板】线段树1和P3373 【模板】线段树 2
  9. 【Quectel移远展锐平台5G模组RX500U/RG200U使用指南(三)-PCIE】
  10. 手机端页面的自适应设计的开发思路
  11. 应对考试的计算程序复杂度。。。欸
  12. 小程序城市按首字母排序(如同苹果手机通讯录一样得效果)
  13. centos 关机和重启命令
  14. H3C产品的默认密码是多少?
  15. Unity3d 屏幕空间人体皮肤知觉渲染次表面散射Screen-Space Perceptual Rendering Subsurface Scattering of Human Skin
  16. 比较全面的随机森林算法总结
  17. 环保在线监测系统,网页版,提供源码
  18. 【调剂】211北京邮电大学2020年软件学院硕士研究生招生缺额信息
  19. 在职阿里8年,一个31岁女软件测试工程师的心声
  20. [高项]行政收尾VS合同收尾

热门文章

  1. python清空列表_Python之列表
  2. JAVA写同步栈_tomcat实现的同步队列和同步栈
  3. ubuntu默认root密码
  4. docker访问宿主机mysql_docker容器内访问宿主机127.0.0.1服务
  5. 【转】用fo-dicom实现print scu的注意事项!!!!!!!!!
  6. 【转】01Teams的前世今生
  7. ASP.Net请求处理机制初步探索之旅 - Part 3 管道
  8. 认识ASP.NET 5项目结构和项目文件xproj
  9. 26享元模式(Flyweight Pattern)
  10. 第五节: Quartz.Net五大构件之Trigger的四大触发类