正文

一、定义

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

  • 组合对象:包含其他组件的组件。
  • 个体对象(叶节点对象):没有包含其他组件的组件。

要点:

  • 组合结构内的任意对象称为组件,组件可以是组合,也可以是叶节点。
  • 通过将组合对象和个体对象放在树形结构中,我们创建了一个“整体/部分”层次结构。如果将整个树形结构视为一个“大组合”的话,那么这个树形结构的每一个“子树形结构”也是一个组合,包括叶节点也可以被视为一个不包含其他对象的组合。这样一来,我们就有了“以一致的方式处理”的基础了。
  • 所谓“以一致的方式处理”,是指组合和叶节点具有共同的方法可以调用。这就要求它们必须实现相同的接口。
  • 组合模式允许客户对组合对象和个体对象一视同仁。换句话说,我们可以把相同的操作应用在组合对象和个体对象上。

二、实现步骤

1、创建组件抽象类

也可以使用组件接口。

组件中有些方法可能不适合某种对象,此时我们可以抛异常或者提供特定实现。

/**
* 组件抽象类
*/
public abstract class Component { /**
* 子组件(可以是组合或叶节点)
*/
protected List<Component> childs = new ArrayList<Component>(); /**
* 添加子组件
*/
public void addChild(Component component) {
childs.add(component);
} /**
* 移除子组件
*/
public void removeChild(Component component) {
childs.remove(component);
} /**
* 获取所有子组件
*/
public List<Component> getChilds() {
return childs;
} public String getName() {
// 默认抛异常,由子类决定要不要覆盖
throw new UnsupportedOperationException();
}
}

2、创建组合及叶节点,并继承组件抽象类

(1)组合

组合可以包含其他组合,也可以包含叶节点。

/**
* 组合
*/
public class Composite extends Component { private String name; public Composite(String name) {
this.name = name;
} @Override
public String getName() {
return name;
}
}

(2)叶节点

叶节点无法添加、删除、获取子节点,因此需要对相应的方法进行特殊处理。

/**
* 叶节点
*/
public class Leaf extends Component { private String name; public Leaf(String name) {
this.name = name;
} @Override
public void addChild(Component component) {
// 叶节点不能添加子节点,可以抛异常或者空实现
throw new UnsupportedOperationException();
} @Override
public void removeChild(Component component) {
// 叶节点没有子节点可移除,可以抛异常或者空实现
throw new UnsupportedOperationException();
} @Override
public List<Component> getChilds() {
// 叶节点没有子节点,可以抛异常或者返回空集合
throw new UnsupportedOperationException();
} @Override
public String getName() {
return name;
}
}

3、统一使用组件的方法,操作组合及叶节点

由于组合及叶节点都实现了组件接口,因此可以使用组件的方法来操作组合及叶节点。

public class Test {

    public static void main(String[] args) {
// 组合
Component composite1 = new Composite("composite1");
Component composite2 = new Composite("composite2");
Component composite3 = new Composite("composite3");
// 叶节点
Component leaf1 = new Leaf("leaf1");
Component leaf2 = new Leaf("leaf2");
Component leaf3 = new Leaf("leaf3");
Component leaf4 = new Leaf("leaf4"); // 组合1包含组合2、3
composite1.addChild(composite2);
composite1.addChild(composite3);
// 组合2包含叶节点1、2
composite2.addChild(leaf1);
composite2.addChild(leaf2);
// 组合3包含叶节点3、4
composite3.addChild(leaf3);
composite3.addChild(leaf4); // 打印组件名称
System.out.println(composite1.getName());
for (Component child : composite1.getChilds()) {
System.out.println(" " + child.getName());
for (Component leaf : child.getChilds()) {
System.out.println(" " + leaf.getName());
}
}
}
}

三、举个栗子

1、背景

对象村餐厅和对象村煎饼屋合并了,它们合并后的新公司创建了一个 Java 版本的女招待。这个 Java 版本的女招待能够打印使用 ArrayList 存储的餐厅菜单和煎饼屋菜单。

现在它们打算加上一份餐后甜点的“子菜单”。也就是说,这个 Java 版本的女招待不仅要支持打印多个菜单,还要支持打印菜单中的菜单。

2、实现

由于菜单可能包含菜单和菜单项,因此我们可以创建一个树形结构,这个树形结构由菜单和菜单项组成。

通过将菜单和菜单项放在相同的结构中,我们既可以把整个结构视为一个“大菜单”,也可以把这个结构的任一部分视为一个“子菜单”,包括菜单项也可以视为一个“我本身就是菜单项因此没必要再包含菜单项的菜单”。这样,我们就可以以一致的方式来处理菜单和菜单项了。

(1)创建菜单组件抽象类

/**
* 菜单组件抽象类
*/
public abstract class MenuComponent { /**
* 子组件(可以是菜单或菜单项)
*/
protected List<MenuComponent> childs = new ArrayList<MenuComponent>(); /**
* 添加子组件
*/
public void addChild(MenuComponent component) {
childs.add(component);
} /**
* 移除子组件
*/
public void removeChild(MenuComponent component) {
childs.remove(component);
} /**
* 获取所有子组件
*/
public List<MenuComponent> getChilds() {
return childs;
} public String getName() {
throw new UnsupportedOperationException();
} public double getPrice() {
throw new UnsupportedOperationException();
} public void print() {
throw new UnsupportedOperationException();
}
}

(2)创建菜单,并继承菜单组件抽象类

/**
* 菜单(组合)
*/
public class Menu extends MenuComponent { private String name; public Menu(String name) {
this.name = name;
} @Override
public String getName() {
return name;
} @Override
public void print() {
System.out.println("\n" + getName());;
System.out.println("---------------------");;
}
}

(3)创建菜单项,并继承菜单组件抽象类

/**
* 菜单项(叶节点)
*/
public class MenuItem extends MenuComponent { private String name;
private double price; public MenuItem(String name, double price) {
this.name = name;
this.price = price;
} @Override
public void addChild(MenuComponent component) {
throw new UnsupportedOperationException();
} @Override
public void removeChild(MenuComponent component) {
throw new UnsupportedOperationException();
} @Override
public List<MenuComponent> getChilds() {
return childs;
} @Override
public String getName() {
return name;
} @Override
public double getPrice() {
return price;
} @Override
public void print() {
System.out.println(" " + getName() + ", " + getPrice());;
}
}

(4)创建女招待

/**
* 女招待
*/
public class Waitress { MenuComponent allMenus; public Waitress(MenuComponent allMenus) {
this.allMenus = allMenus;
} public void printMenu() {
print(allMenus);
} private void print(MenuComponent menuComponent) {
menuComponent.print();
for (MenuComponent child : menuComponent.getChilds()) {
print(child);
}
}
}

(5)使用女招待打印菜单

public class Test {

    public static void main(String[] args) {
// 所有菜单
MenuComponent allMenus = new Menu("ALL MENUS");
// 子菜单
MenuComponent pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU");
MenuComponent dinerMenu = new Menu("DINER MENU");
MenuComponent dessertMenu = new Menu("DESSERT MENU"); // 添加煎饼屋菜单及菜单项
allMenus.addChild(pancakeHouseMenu);
pancakeHouseMenu.addChild(new MenuItem("Regular Pancake Breakfast", 2.99));
pancakeHouseMenu.addChild(new MenuItem("Blueberry Pancakes", 3.49));
pancakeHouseMenu.addChild(new MenuItem("Waffles", 3.59));
// 添加餐厅菜单及菜单项
allMenus.addChild(dinerMenu);
dinerMenu.addChild(new MenuItem("BLT", 2.99));
dinerMenu.addChild(new MenuItem("Soup of the day", 3.29));
dinerMenu.addChild(new MenuItem("Hotdog", 3.05));
// 添加甜点菜单及菜单项
dinerMenu.addChild(dessertMenu);
dessertMenu.addChild(new MenuItem("Apple Pie", 1.59));
dessertMenu.addChild(new MenuItem("Cheesecake", 1.99));
dessertMenu.addChild(new MenuItem("Sorbet", 1.89)); // 使用女招待打印菜单
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
}

《Head First 设计模式》:组合模式的更多相关文章

  1. 16. 星际争霸之php设计模式--组合模式

    题记==============================================================================本php设计模式专辑来源于博客(jymo ...

  2. Java设计模式——组合模式

    JAVA 设计模式 组合模式 用途 组合模式 (Component) 将对象组合成树形结构以表示“部分-整体”的层次结构.组合模式使得用户对单个对象和组合对象的使用具有唯一性. 组合模式是一种结构型模 ...

  3. 【设计模式】Java设计模式 - 组合模式

    Java设计模式 - 组合模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自己 ...

  4. c#设计模式-组合模式

    在软件开发过程中,我们经常会遇到处理简单对象和复合对象的情况,例如对操作系统中目录的处理就是这样的一个例子,因为目录可以包括单独的文件,也可以包括文件夹,文件夹又是由文件组成的,由于简单对象和复合对象 ...

  5. [Head First设计模式]生活中学设计模式——组合模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  6. JAVA 设计模式 组合模式

    用途 组合模式 (Component) 将对象组合成树形结构以表示“部分-整体”的层次结构.组合模式使得用户对单个对象和组合对象的使用具有唯一性. 组合模式是一种结构型模式. 结构

  7. javascript设计模式-组合模式

    组合模式所要解决的问题: 可以使用简单的对象组合成复杂的对象,而这个复杂对象有可以组合成更大的对象.可以把简单这些对象定义成类,然后定义一些容器类来存储这些简单对象. 客户端代码必须区别对象简单对象和 ...

  8. 设计模式组合模式(Composite)精华

    23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例,他们帮助如何创建一个系统独立.这是一个这些对象和陈述的组合. 创建使用继承类的类架构更改实例.的对象类型模型的建 ...

  9. 设计模式 -- 组合模式 (Composite Pattern)

    定义: 对象组合成部分整体结构,单个对象和组合对象具有一致性. 看了下大概结构就是集团总公司和子公司那种层级结构. 角色介绍: Component :抽象根节点:其实相当去总公司,抽象子类共有的方法: ...

  10. javascript设计模式——组合模式

    前面的话 在程序设计中,有一些和“事物是由相似的子事物构成”类似的思想.组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的.本文将详细介绍组合模式 宏命令 宏 ...

随机推荐

  1. 解Bug之路-dubbo流量上线时的非平滑问题

    前言 笔者最近解决了一个困扰了业务系统很久的问题.这个问题只在发布时出现,每次只影响一两次调用,相较于其它的问题来说,这个问题有点不够受重视.由于种种原因,使得这个问题到了业务必须解决的程度,于是就到 ...

  2. troubleshoot之:使用JFR解决内存泄露

    目录 简介 一个内存泄露的例子 使用JFR和JMC来分析内存泄露 OldObjectSample 总结 简介 虽然java有自动化的GC,但是还会有内存泄露的情况.当然java中的内存泄露跟C++中的 ...

  3. 【NOI2014】动物园 - KMP

    题目描述 近日,园长发现动物园中好吃懒做的动物越来越多了.例如企鹅,只会卖萌向游客要吃的.为了整治动物园的不良风气,让动物们凭自己的真才实学向游客要吃的,园长决定开设算法班,让动物们学习算法. 某天, ...

  4. Url跳转漏洞常见

    Url跳转漏洞常见出现点: 1.用户登录.统一身份认证处,认证完后会跳转. 2.用户分享.收藏内容过后,会跳转. 3.跨站点认证.授权后,会跳转. 4.站内点击其它网址链接时,会跳转. Url跳转漏洞 ...

  5. java+opencv人脸识别程序2.0

    由于第一次写的太粗糙了,所以又修改了一下,详细的更改如下: @ 目录 更改 窗口问题 识别问题 相似度对比 仍然存在的问题 人脸信息显示 图片质量 更改 优化了一下界面风格 窗口问题 原来是在主界面外 ...

  6. java实现高斯平滑

    高斯模糊也叫作高斯平滑,这里主要用来实现图像降噪.官方有入门教程:http://opencv-java-tutorials.readthedocs.io/en/latest/ 实现代码如下: pack ...

  7. JavaScript学习系列博客_31_JavaScript Math 工具类

    Math - Math属于一个工具类,它不需要我们创建对象(例如Date日期对象需要通过构造函数创建对象 var 变量=new Date(),Math不需要),它里边封装了属性运算相关的常量和方法 我 ...

  8. pandas电子表格的读取(pandas中的read_excel)

    上面那篇文章中,初步介绍了一个文本文件的读取:接下来介绍另外一种常见的本地数据格式,那就是Excel电子表格,如果读者在学习或者工作中需要使用Python分析某个Excel表格数据,改如何完成第一个的 ...

  9. akka-grpc - 基于akka-http和akka-streams的scala gRPC开发工具

    关于grpc,在前面的scalaPB讨论里已经做了详细的介绍:google gRPC是一种全新的RPC框架,在开源前一直是google内部使用的集成工具.gRPC支持通过http/2实现protobu ...

  10. promise的常用情况

    因为js是单线程的,所以一旦代码中有报错,就不会执行下面的了,如下333就未打印 console.log(111)throw Error(222)console.log(333) 好像与promise ...