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

组合模式

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

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

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

组合模式树形结构

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

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

定义

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

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

图片说明

​ 图片说明

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

图片说明

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

    }

}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445347.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python(4)--Pycharm安装、使用小技巧

Pycharm安装1.专业版Pycharm 安装2.设置Pycharm桌面快捷图标3.Linux卸载一个软件4.教育版Pycharm的安装5.多文件项目演练&#xff08;Pycharm针对学生和教师开发了免费使用版&#xff09;1.专业版Pycharm 安装 1.官网下载安装包 .tar.gz 2.解压缩 tar -zxvf 文件名 3.移动解压…

推荐算法--推荐系统冷启动问题(03)

文章目录目录1.什么是冷启动问题&#xff1f;1.1冷启动问题1.2 冷启动问题的分类1. 用户冷启动2 物品冷启动3 系统冷启动2.如何解决冷启动问题&#xff1f;2.1利用用户注册信息2.2选择合适的物品启动用户的兴趣2.3利用物品的内容信息2.4 发挥专家的作用目录 1.什么是冷启动问题…

《Head First 设计模式》第十章-状态模式 状态模式

状态模式 策略模式和状态模式是双胞胎&#xff0c;在出生时才分开。你已经知道&#xff0c;策略模式是围绕可以互换的算法来创建成功业务的&#xff0c;然而&#xff0c;状态走的是更崇高的路&#xff0c;它通过改变对象内部的状态来帮助对象控制自己的行为。 定义状态模式 …

推荐算法--利用用户标签数据(04)

文章目录流行的推荐系统通过3种方式联系用户兴趣和物品 &#xff08;1&#xff09;&#xff1a;利用用户喜欢过的物品&#xff0c;给用户推荐与他喜欢过的物品相似的物品&#xff0c;这是基于物品的算法。 &#xff08;2&#xff09;&#xff1a;利用和用户兴趣相似的其他用户…

Python(5)-注释

Python注释1.单行注释2. 多行注释&#xff08;块注释&#xff09;3.注释的使用和代码规范pyhton 的注释 使用自己熟悉的语言&#xff08;中文&#xff09;&#xff0c;解释代码。Python解释器在执行文件时不会执行井号右边边的内容。1.单行注释 # 井号后面跟着注释内容 灰灰的虚…

网络原理知识点总结

第一章&#xff1a; 计算机网络系统由资源子网和通信子网组成。 计算机网络系统主要由网络通信系统、操作系统和应用系统构成 互联网基础结构发展的三个阶段&#xff1a; 第一阶段&#xff1a;从单个网络 ARPANET 向互联网发展的过程。 第二阶段&#xff1a;建成了三级结构…

推荐算法--时效性(05)

时效性 推荐系统应该考虑时间效应&#xff0c;因为用户的兴趣是有时间变化的。用户一年前喜欢的东西现在不一定感兴趣&#xff0c;相比于推荐过去喜欢的物品&#xff0c;推荐用户近期喜欢的物品更有参考价值。而在新闻更是如此&#xff0c;推荐过去跟用户兴趣一致的新闻已经失去…

推荐算法--推荐系统架构(06)

外围架构一般来说&#xff0c;每个网站都有一个 UI 系统&#xff0c;UI 系统负责给用户展示网页并和用户交互。网站会通过日志系统将用户在 UI 上的各种各样的行为记录到用户行为日志中。 从上面的结构可以看到&#xff0c;除了推荐系统本身&#xff0c;主要还依赖两个条件--界…

Python(6)-算数运算符

算数运算符1.算数运算符2.优先级1.算数运算符 加 减- 乘* 除/ 取商// 取余数% 幂**(能算n次方&#xff1a; 2**38&#xff0c;一直以为只能算平方) 扩展&#xff1a; 乘法用于字符串&#xff1a;字符串重复指定的次数&#xff0c;要拼接的次数很长时&#xff0c;用乘号很方便…

推荐算法--其他信息(07)

文章目录目录1.利用上下文信息1.1时间上下文1.2地点上下文2.利用网络社交数据2.1 获取网络社交数据途径2.2 社交网络数据2.3 基于社交网络的推荐2.4 推荐算法2.5 给用户推荐好友目录 1.利用上下文信息 1.1时间上下文 用户的兴趣是随着时间变化的&#xff0c;三天打鱼两天晒网…

Python(7)-程序执行的原理

程序执行的原理1.计算机中的三个核心部件2.程序执行的原理3.程序的作用1.计算机中的三个核心部件 CPU&#xff1a;中央处理区&#xff0c;超大规模的集成电路&#xff0c;负责处理数据、计算 内存&#xff1a;临时存储数据&#xff0c;断电数据消失&#xff0c;读取数据快 硬盘…

橙白oj 2017级《算法分析与设计》-练习02

注&#xff1a;A题我以为给新生出的&#xff0c;应该贼简单&#xff0c;是按顺序消灭&#xff0c;卡了十几分钟&#xff0c;成了最后一个ac的题&#xff0c;真是菜的真实。 Problem A: Description 白细胞是人体与疾病斗争的“卫士”。当病菌侵入人体体内时&#xff0c;白细胞…

python(9)-变量、input函数

变量、input函数1.变量的定义(不可变对象)2.变量的类型3.变量的命名规范4. 不同类型的数据计算5.类型转换函数6.input()7.Tips程序是用来处理数据的&#xff0c;而变量是用来存储数据的。 关于函数&#xff0c;是一个提前准备好的代码&#xff1b;可以直接使用&#xff0c;不用…

推荐算法--总结(08)

一、推荐系统结构二、推荐引擎算法&#xff08;Algorithm&#xff09;1、协同过滤推荐算法1.1 关系矩阵与矩阵计算1.1.1 用户与用户&#xff08;U-U矩阵&#xff09;1.1.2 物品与物品&#xff08;V-V矩阵&#xff09;1.1.3 用户与物品&#xff08;U-V矩阵&#xff09;1.1.4 奇异…

算法总结-1算法入门

1.0 前言 算法&#xff08;Algorithm&#xff09;是指解题方案的准确而完整的描述&#xff0c;是一系列解决问题的清晰指令&#xff0c;算法代表着用系统的方法描述解决问题的策略机制。也就是说&#xff0c;能够对一定规范的输入&#xff0c;在有限时间内获得所要求的输出。 …

FM系列算法解读(FM+FFM+DeepFM)

在计算广告中&#xff0c;CTR是非常重要的一环。对于特征组合来说&#xff0c;业界通用的做法主要有两大类&#xff1a;FM系列和Tree系列。这里我们来介绍一下FM系列。   在传统的线性模型中&#xff0c;每个特征都是独立的&#xff0c;如果需要考虑特征与特征之间的相互作用…

二叉树层序遍历

层序遍历序列为&#xff1a;ABCDEFG 思路&#xff1a;栈是先进后出的数据结构&#xff0c;而队列是先进先出的数据结构。 我们层序遍历&#xff0c;很明显&#xff0c;先遇到的节点先打印&#xff0c;不同于前中后序遍历&#xff0c;我们采用队列结构。 具体执行过程如下&…

深度学习(01)-- 基础学习

文章目录目录1. 深度学习基础1.1 深度学习总览1.2 深度网络训练过程1.2.1 传统神经网络的训练方法为什么不能用在深度神经网络1.2.2 deep learning训练过程1.3 数学知识&#xff1a;2. 九种深度学习模型2.1 受限玻尔兹曼机RBM2.2 自编码器AE&#xff08;降维&#xff09;2.3 深…

MachineLearning(1)-激活函数sigmoid、损失函数MSE、CrossEntropyLoss

损失函数1.激活函数2.损失函数2.1均方误差损失函数2.2交叉熵损失函数2.3 NLLLoss()2.4 BCELoss()1.激活函数 全连接网络又叫多层感知器&#xff0c;多层感知器的基本单元神经元是模仿人类神经元兴奋与抑制机制&#xff0c;对其输入进行加权求和&#xff0c;若超过某一阈值则该…

Java的IO总结

非流式文件类--File类 从定义看&#xff0c;File类是Object的直接子类&#xff0c;同时它继承了Comparable接口可以进行数组的排序。 File类的操作包括文件的创建、删除、重命名、得到路径、创建时间等&#xff0c;以下是文件操作常用的函数。 File类是对文件系统中文件以及文…