本文出自   http://blog.csdn.net/shuangde800


认识组合模式

上一篇中,我们可以用迭代器来实现遍历一个集合(数组,ArrayList, Vector, HashTable等)。

假设有这样一种集合结构i:餐厅里有一份菜单,菜单里面还有子菜单,其实就是一个树形的结构

那么,之前的迭代器就不能用了。

我们需要新的设计:

1. 需要某种树形结构,可以容纳菜单,子菜单和菜单项

2. 需要确定能够在每个菜单的各个项之间游走,而且至少要像现在用迭代器一样方便

3. 我们也需要更有弹性地在菜单之间游走。比方说,可能只需要遍历甜点菜单,或者可以遍历餐厅的整个菜单

定义组合模式

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

有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

组合模式让你可以优化处理递归或分级数据结构。有许多关于分级数据结构的例子,使得组合模式非常有用武之地。关于分级数据结构的一个普遍性的例子是你每次使用电脑时所遇到的:文件系统。文件系统由目录和文件组成。每个目录都可以装内容。目录的内容可以是文件,也可以是目录。按照这种方式,计算机的文件系统就是以递归结构来组织的。如果你想要描述这样的数据结构,那么你可以使用组合模式Composite。

以嵌套菜单为例,这个模式能够创建一个树形结构,在同一个结构中处理嵌套菜单和菜单项组。通过将菜单和项放在相同的结构中,我们创建了一个“整体/部分”层次结构,即由菜单和菜单项组成的对象树。但是可以将它视为一个整体,像是一个丰富的大菜单。

一旦有了丰富的“大菜单”,我们就可以用这个模式来“统一处理个别对象和组合对象”。

这意味着,如果我们有了一个树形结构的菜单,子菜单和可能还带有菜单项的子菜单,那么任何一个菜单都是一种“组合”。因为它既可以包含其他菜单,也可以包含菜单项。我们可以忽略对象组合和个别对象之间的差别。

使用组合模式,只要写出简单的代码,就能够对整个菜单结构应用相同的操作!

利用组合设计菜单

1. 实现菜单组件

// 菜单组件的抽象类
// 菜单组件的角色是为叶子节点和组合节点提供一个共同的接口
// 所有的组件都必须实现MenuComponent接口
// 但是叶节点和在组合节点的角色不同,所以有些方法可能不适合某些节点
// 面对这种情况,最好抛出运行时异常
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 void print() {
throw new UnsupportedOperationException();
}
}

2. 实现菜单项

// 这是组合类图的叶类,这里是菜单项
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;
} // 这里和之前的实现不一样
// 我们覆盖了print()方法
// 对菜单项来说,此方法会打印出完整的菜单条目
// 包括名字,描述,价格等
public void print() {
System.out.print(" " + getName());
if (isVegetarian()) {
System.out.print("(v)");
}
System.out.println(", " + getPrice());
System.out.println(" -- " + getDescription());
}
}

3. 实现组合菜单

public class Menu extends MenuComponent {
// 菜单可以有任意数目的儿子
// 且这些儿子必须属于MenuComonent类型
// 在内部ArrayList里记录它们
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 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();
}
}
}

组合迭代器

组合模式的迭代器需要用递归来实现

public class CompositeIterator implements Iterator {
Stack stack = new Stack(); // 将我们需要遍历的顶层组合的迭代器传入
// 放进一个栈数据结构中
public CompositeIterator(Iterator iterator) {
stack.push(iterator);
} // 当客户想要取得下一个元素的时候
// 我们先调用hashNext()来判断是否还有下一个元素
public Object next() {
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;
}
} public boolean hasNext() {
if (stack.empty()) { //如果栈空了,说明没有下一个元素了
return false;
} else {
Iterator iterator = (Iterator) stack.peek();
if (!iterator.hasNext()) {
stack.pop();
return hasNext();
} else {
return true;
}
}
} // 不支持删除
public void remove() {
throw new UnsupportedOperationException();
}
}

空迭代器

先要一个组合迭代器,那么每个组件都要加上createIterator()方法。

在菜单中实现:
public class Menu extends MenuComponent {

    // 其它部分代码不需要修改

    public Iterator createIterator() {
return new CompositeIterator(menuComponents.iterator()) ;
}
}

但是在菜单项中要怎样实现呢?我们知道菜单项是叶子节点,没有什么可以遍历了。


有两种选择:
选择一: 返回null
我们可以让createItrerator()方法返回null,但是如果这样做,我们的客户代码就要条件语句来判断返回值是否为null
选择二:返回一个迭代器,这个迭代器的hasNext()永远返回false
这个方法显然更好,客户就不必担心是否为null了。
public class NullIterator implements Iterator {

	public Object next() {
return null;
} public boolean hasNext() {
return false;
} public void remove() {
throw new UnsupportedOperationException();
}
}

菜单项实现createIterator()方法

public class MenuItem extends MenuComponent {

    // 其它部分代码不需要修改

    public Iterator createIterator() {
return new NullIterator(); // 返回空迭代器
}
}



【设计模式】学习笔记13:组合模式(Composite)的更多相关文章

  1. 设计模式(七)组合模式Composite(结构型)

    设计模式(七)组合模式Composite(结构型) 1. 概述 在数据结构里面,树结构是很重要,我们可以把树的结构应用到设计模式里面. 例子1:就是多级树形菜单. 例子2:文件和文件夹目录 2.问题 ...

  2. 设计模式学习笔记--备忘录(Mamento)模式

    写在模式学习之前 什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方式,这就是软件模式:每个模式描写叙述了一个在我们程序设计中常常发生的问题,以及该问题的解决方式:当我们碰到模 ...

  3. javascript设计模式学习之十——组合模式

    一.组合模式定义及使用场景 组合模式将对象组合成树形结构,用以表示“部分—整体”的层次结构,除了用来表示树形结构之外,组合模式还可以利用对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性. ...

  4. 13组合模式Composite

    一.什么是组合模式 Composite模式也叫组合模式,是构造型的设 计模式之一.通过递归手段来构造树形的对象结 构,并可以通过一个对象来访问整个对象树. 二.组合模式的结构 三.组合模式的角色和职责 ...

  5. 设计模式(八)组合模式 Composite

    组合模式: 允许你将对象组合成树形结构来表现“整体/部分”层次结构.组合能让客户以一致的方式处理个别对象以及对象组合. 组合模式适用于创建复杂的对象,这个对象包含某些个别的对象以及这些对象的组合. 从 ...

  6. 设计模式学习笔记——Bridge 桥接模式

    先说一下我以前对桥接模式的理解:当每个类中都使用到了同样的属性或方法时,应该将他们单独抽象出来,变成这些类的属性和方法(避免重复造轮子),当时的感觉是和三层模型中的model有点单相似,也就是让mod ...

  7. 设计模式学习笔记-Adapter模式

    Adapter模式,就是适配器模式,使两个原本没有关联的类结合一起使用. 平时我们会经常碰到这样的情况,有了两个现成的类,它们之间没有什么联系,但是我们现在既想用其中一个类的方法,同时也想用另外一个类 ...

  8. Java 设计模式学习笔记1——策略模式(Duck例子)

    0.假设现有工程(Duck)中遇到为类添加功能的问题,如何设计类添加新的功能? 1.利用继承提供的Duck(鸭子)的行为会导致哪些缺点? (1)代码在多个子类中重复 (2)很多男知道所有鸭子的全部行为 ...

  9. Java-马士兵设计模式学习笔记-装饰者模式

    Java装饰者模式简介 一.假设有一个Worker接口,它有一个doSomething方法,Plumber和Carpenter都实现了Worker接口,代码及关系如下: 1.Worker.java p ...

  10. 研磨设计模式学习笔记2--外观模式Facade

    需求:客户端需要按照需求,执行一个操作,操作包括一个系统中的3个模块(根据配置选择是否全部执行). 外观模式优点: 客户端无需知道系统内部实现,,只需要写好配置文件,控制那些模块执行,简单易用. 外观 ...

随机推荐

  1. 剑指offer-面试题10:二进制中1的个数

    题目:请实现一个函数,输入一个函数,输出该数二进制表示中1的个数.例如把9 表示成二进制是1001,有2位是1.因此如果输入9,该函数输出2. 这道题最典型的方法就是用移位统计,就比如统计9的二进制1 ...

  2. Clone使用方法详解【转载】

    博客引用地址:Clone使用方法详解 Clone使用方法详解   java“指针”       Java语言的一个优点就是取消了指针的概念,但也导致了许多程序员在编程中常常忽略了对象与引用的区别,本文 ...

  3. hdu 5631 Rikka with Graph(图)

    n个点最少要n-1条边才能连通,可以删除一条边,最多删除2条边,然后枚举删除的1条边或2条边,用并查集判断是否连通,时间复杂度为O(n^3) 这边犯了个错误, for(int i=0;i<N;i ...

  4. ios常用的几个动画代码

      #import "MianViewController.h" #import <QuartzCore/QuartzCore.h> @interface MianVi ...

  5. Spring-Junit4

      Spring整合Junit4测试Service 1. 加入依赖包 使用Spring的测试框架需要加入以下依赖包: JUnit 4 (官方下载:https://github.com/KentBeck ...

  6. 为啥NSString的属性要用copy而不用retain

    之前学习生活中,知道NSString的属性要用copy而不用retain,可是不知道为啥,这两天我研究了一下,然后最终明确了. 详细原因是由于用copy比用retain安全,当是NSString的时候 ...

  7. Mongodb中Sharding集群

    随着mongodb数据量的增多,可能会达到单个节点的存储能力限制,以及application较大的访问量也会导致单个节点无法承担,所以此时需要构建集群环境,并通过sharding方案将整个数据集拆分成 ...

  8. SqlServer判断数据库、表、存储过程、函数是否存在

    假设场景是: 需要给一个脚本给客户更新, 这个对象可能存在或不存在 -- 更新存储过程 USE [数据库名] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ...

  9. SqlLikeAttribute 特性增加 左、右Like实现

    SqlLikeAttribute 特性原来只实现了全Like,今天增加左.右Like实现 更新时间:2016-04-30 /// <summary> /// 获取查询条件语句 /// &l ...

  10. USACO Chapter 1 解题总结

    USACO Chapter 1 解题总结 1.1.1 Your Ride Is Here 基本字符串操作,无压力. 1.1.2 Greedy Gift Givers 基础模拟题,弄明白题意,不怕麻烦, ...