在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣服与衣柜、以及厨房中的锅碗瓢盆等。在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。

组合模式的定义与特点

组合(Composite Pattern)模式的定义:有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。

组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下。

由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。

这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。

组合模式的主要优点有:

  1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;

  2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

  1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;

  2. 不容易限制容器中的构件;

  3. 不容易用继承的方法来增加构件的新功能;

组合模式的结构与实现

组合模式的结构不是很复杂,下面对它的结构和实现进行分析。

1. 模式的结构

组合模式包含以下主要角色。

  1. 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)

  2. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。

  3. 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

组合模式分为透明式的组合模式和安全式的组合模式。

透明方式

在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。其结构图如图 1 所示。

安全方式

在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。其结构图如图 2 所示。

2. 模式的实现

假如要访问集合 c0={leaf1,{leaf2,leaf3}} 中的元素,其对应的树状图如图 3 所示。

透明组合模式

下面为透明式的组合模式的实现代码。

public class CompositePattern {
public static void main(String[] args) {
Component c0 = new Composite();
Component c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
} //抽象构件
interface Component {
public void add(Component c); public void remove(Component c); public Component getChild(int i); public void operation();
} //树叶构件
class Leaf implements Component {
private String name; public Leaf(String name) {
this.name = name;
} public void add(Component c) {
} public void remove(Component c) {
} public Component getChild(int i) {
return null;
} public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
} //树枝构件
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>(); public void add(Component c) {
children.add(c);
} public void remove(Component c) {
children.remove(c);
} public Component getChild(int i) {
return children.get(i);
} public void operation() {
for (Object obj : children) {
((Component) obj).operation();
}
}
}

程序运行结果如下:

树叶1:被访问!
树叶2:被访问!
树叶3:被访问! 

安全组合模式

安全式的组合模式与透明式组合模式的实现代码类似,只要对其做简单修改就可以了,代码如下。

首先修改 Component 代码,只保留层次的公共行为。

interface Component {
public void operation();
}

然后修改客户端代码,将树枝构件类型更改为 Composite 类型,以便获取管理子类操作的方法。

public class CompositePattern {
public static void main(String[] args) {
Composite c0 = new Composite();
Composite c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}

组合模式的应用实例

【例1】用组合模式实现当用户在商店购物后,显示其所选商品信息,并计算所选商品总价的功能。

说明:假如李先生到韶关“天街e角”生活用品店购物,用 1 个红色小袋子装了 2 包婺源特产(单价 7.9 元)、1 张婺源地图(单价 9.9 元);用 1 个白色小袋子装了 2 包韶关香藉(单价 68 元)和 3 包韶关红茶(单价 180 元);用 1 个中袋子装了前面的红色小袋子和 1 个景德镇瓷器(单价 380 元);用 1 个大袋子装了前面的中袋子、白色小袋子和 1 双李宁牌运动鞋(单价 198 元)。

最后“大袋子”中的内容有:{1 双李宁牌运动鞋(单价 198 元)、白色小袋子{2 包韶关香菇(单价 68 元)、3 包韶关红茶(单价 180 元)}、中袋子{1 个景德镇瓷器(单价 380 元)、红色小袋子{2 包婺源特产(单价 7.9 元)、1 张婺源地图(单价 9.9 元)}}},现在要求编程显示李先生放在大袋子中的所有商品信息并计算要支付的总价。

本实例可按安全组合模式设计,其结构图如图 4 所示。

代码如下:

package composite;

import java.util.ArrayList;

public class ShoppingTest {
public static void main(String[] args) {
float s = 0;
Bags BigBag, mediumBag, smallRedBag, smallWhiteBag;
Goods sp;
BigBag = new Bags("大袋子");
mediumBag = new Bags("中袋子");
smallRedBag = new Bags("红色小袋子");
smallWhiteBag = new Bags("白色小袋子");
sp = new Goods("婺源特产", 2, 7.9f);
smallRedBag.add(sp);
sp = new Goods("婺源地图", 1, 9.9f);
smallRedBag.add(sp);
sp = new Goods("韶关香菇", 2, 68);
smallWhiteBag.add(sp);
sp = new Goods("韶关红茶", 3, 180);
smallWhiteBag.add(sp);
sp = new Goods("景德镇瓷器", 1, 380);
mediumBag.add(sp);
mediumBag.add(smallRedBag);
sp = new Goods("李宁牌运动鞋", 1, 198);
BigBag.add(sp);
BigBag.add(smallWhiteBag);
BigBag.add(mediumBag);
System.out.println("您选购的商品有:");
BigBag.show();
s = BigBag.calculation();
System.out.println("要支付的总价是:" + s + "元");
}
} //抽象构件:物品
interface Articles {
public float calculation(); //计算 public void show();
} //树叶构件:商品
class Goods implements Articles {
private String name; //名字
private int quantity; //数量
private float unitPrice; //单价 public Goods(String name, int quantity, float unitPrice) {
this.name = name;
this.quantity = quantity;
this.unitPrice = unitPrice;
} public float calculation() {
return quantity * unitPrice;
} public void show() {
System.out.println(name + "(数量:" + quantity + ",单价:" + unitPrice + "元)");
}
} //树枝构件:袋子
class Bags implements Articles {
private String name; //名字
private ArrayList<Articles> bags = new ArrayList<Articles>(); public Bags(String name) {
this.name = name;
} public void add(Articles c) {
bags.add(c);
} public void remove(Articles c) {
bags.remove(c);
} public Articles getChild(int i) {
return bags.get(i);
} public float calculation() {
float s = 0;
for (Object obj : bags) {
s += ((Articles) obj).calculation();
}
return s;
} public void show() {
for (Object obj : bags) {
((Articles) obj).show();
}
}
}

程序运行结果如下:

您选购的商品有:
李宁牌运动鞋(数量:1,单价:198.0元)
韶关香菇(数量:2,单价:68.0元)
韶关红茶(数量:3,单价:180.0元)
景德镇瓷器(数量:1,单价:380.0元)
婺源特产(数量:2,单价:7.9元)
婺源地图(数量:1,单价:9.9元)
要支付的总价是:1279.7元

组合模式的应用场景

前面分析了组合模式的结构与特点,下面分析它适用的以下应用场景。

  1. 在需要表示一个对象整体与部分的层次结构的场合。

  2. 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。

组合模式的扩展

如果对前面介绍的组合模式中的树叶节点和树枝节点进行抽象,也就是说树叶节点和树枝节点还有子节点,这时组合模式就扩展成复杂的组合模式了,如 Java AWT/Swing 中的简单组件 JTextComponent 有子类 JTextField、JTextArea,容器组件 Container 也有子类 Window、Panel。复杂的组合模式的结构图如图 5 所示。

Android 中的 View 和 ViewGroup

组合模式在 Android 中最经典的用法无非就是 View 和 ViewGroup 的运用了。我们都知道 View 还有 Textview、Button 他们都是继承于 View。他们就像叶子。而 ViewGroup 是容器,ViewGroup 可以添加 View,而 View 不能添加 ViewGroup。这不就是安全组合模式里边 Leaf 和 Node 的关系吗。所以它就是安全组合模式的一种运用。

ViewGroup容器实现方式

接口添加操作子视图方法:

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

首先 ViewGroup 继承自 View,所有 ViewGroup 拥有 view 的公开方法。具有 view 的特性。然后 ViewGroup 继承了两个接口。我们先看 ViewManager 这个接口

public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}

一眼就明白了,跟我们之前的 Composite 一样,添加了 add/remove 等对子视图的操作方法。

在看 ViewParent:

public interface ViewParent {
public void requestLayout();
public void invalidateChild(View child, Rect r);
public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset);
public void requestChildFocus(View child, View focused);
...
}

这里省略了 ViewParent 其它接口,我们会看到我们很多熟悉的方法,请求重新布局、获取子视图焦点等等。这里也是一些对子视图的操作。

ViewGroup 是一个抽象类,通篇 ViewGroup 的代码,ViewGroup 就一个为了把 View 的 onlayout 方法重置为抽象方法。为啥 View 要这么做呢?我们知道 View 的 onlatyou 的方法是,View 在父视图中 layout 布局过后调用 onlayout 方法,onlayout 只是一个空实现,因为 View 已经布局完成。onlayout 没有其他的实现意义。而 ViewGroup 作为一个视图容器,ViewGroup 调用 layout 布局完自己后。还需要布局子视图。所以 View 把 onlayout 重置为抽象方法,让子类必须实现。

参考文章

Java 设计模式 —— 组合模式的更多相关文章

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

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

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

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

  3. JAVA 设计模式 组合模式

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

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

    组合模式是一种常见的设计模式(但我感觉有点复杂)也叫合成模式,有时又叫做部分-整体模式,主要是用来描述部分与整体的关系. 个人理解:组合模式就是将部分组装成整体. 定义如下: 将对象组合成树形结构以表 ...

  5. Java设计模式-组合模式(Composite)

    组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图: 直接来看代码: public class TreeNode { private String name; private ...

  6. Java设计模式----组合模式(Composit )

    1.  组合模式定义: 组合模式,又叫合成模式,有时又叫部分-整体模式,主要用来描述部分与整体的关系. 定义:将对象组合成树形结构以示" 部分--整体 "的层次结构,使得用户对单个 ...

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

    一.组合模式定义 组合模式定义: Compose objects into tree structures to represent part-whole hierarchies. Composite ...

  8. 3.java设计模式-建造者模式

    Java设计模式-建造者模式 在<JAVA与模式>一书中开头是这样描述建造(Builder)模式的: 建造模式是对象的创建模式.建造模式可以将一个产品的内部表象(internal repr ...

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

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

随机推荐

  1. arm-linux openssl移植

    从openssl官网下载openssl https://www.openssl.org/source/ 1.解压到linux中 2. ./config no-asm shared --prefix=$ ...

  2. 说说基于网络的五种IO模型

    # django不是一个异步框架 # tornado是异步的web框架 # 处理每秒大量的请求 # 个人理解的IO:就是应用层与内核驱动层的交互,这个过程无论从应用层到内核中,还是驱动层等待硬件层的数 ...

  3. Bugku-cms1

    一.先用御剑扫描 二.点击第一个目录,发现sql文件 三.把它载下来,用Notepad++打开.发现管理员的账号和密码(admin的密码好像被人改了,然后我用admin888登的后台)  四.后台地址 ...

  4. python-基础入门-2

    这里介绍两个,相当于c中的scanf函数 第一个raw_input 1 age=raw_input("how old are you ") 2 print "you ar ...

  5. centos8 mysql8遇到的问题

    1.装了第一遍,连接没遇到问题,没注意是怎么装的:本机连,外部连都没碰到问题: 遇到了表名大小写的问题,改了配置文件my.cnf或/etc/my.cnf.d/mysql-server.cnf的文件 在 ...

  6. 给力啊!这篇Spring Bean的依赖注入方式笔记总结真的到位,没见过写的这么细的

    1. Bean的依赖注入概念 依赖注入(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现.在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是 ...

  7. MathType中如何编辑求和公式

    在学习过程中,尤其是在写需要用到数学公式的论文的时,需要输入数学公式并进行格式编辑等,那么对于简单的公式可以使用Office自带的公式编辑器,对于复杂的公式建议使用专业的公式编辑器MathType,该 ...

  8. Java 滴IO系统

    JAVA IO 流可以概括为 "两个对应,一个桥梁".两个对应指字节流(Byte Stream)和字符流(Char Stream)的对应,输入流和输出流的对应. 一个桥梁指从字节流 ...

  9. python批量爬取猫咪图片

    不多说直接上代码 首先需要安装需要的库,安装命令如下 pip install BeautifulSoup pip install requests pip install urllib pip ins ...

  10. javaAgent打包找不到premain类文件解决

    agent 作用和开发 可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序.这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等. 由此可知agent ...