组合模式的见解与案例

前言

组合模式(Composite Pattern),又叫部分整体模式,感觉就是把一组类似对象当作一个对象来进行操作,依据树形结构来组合对象,用来表示部分和整体这种结构,他是将类和对象以一定形式组成在一起的,所以属于结构型模式。

主要解决的问题:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦,主要是与树枝和叶子同时实现一个接口来实现的。

举个简单的例子,如下图的文件夹结构,我们对于这个结构肯定会非常熟悉,对于这样的结构我们称之为树形结构。如下图中.java文件我们可以理解为叶子节点,而简单缓存队列批量保存可以当作容器,cache-frame也可以当作容器,他们都可以被双击点开,而容器可以在下面加入新的叶子节点或新的子容器,我们不需要判别是否是容器还是叶子结点,都可以通过双击来遍历这个结构。这就是组合模式的设计动机:组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。

文件夹结构


定义

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

组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关系。同时它也模糊了简单元素(叶子对象)和复杂元素(容器对象)的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客户程序能够与复杂元素的内部结构解耦。

最关键就是叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。


特点

优点:

  1. 高层模块调用简单
  2. 节点可以自由增加

缺点:

  1. 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

结构

主要由如下角色:

  1. 组合接口(Component):组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
  2. 容器对象(Composite):容器对象,定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。
  3. 叶子对象(Leaf):没有子节点

类图:

该类图请配合下面的案例查看

组合模式类图

案例

介绍

最近是第七次人口普查,我们就以人口普查作为案例,我们知道人口普查是一项重要的工作,需要人人都进行配合,才能完成最后的结果,所以一般都是从地方开始统计,在上报到省份直至中央进行统计,如以浙江省为例,需要下面的地级市先开始各自进行统计,如嘉兴市又需要下面的五县两区进行统计,统计好了嘉兴进行汇总报告给浙江省。就是这么一级级下来,而他们都具有一个的共同功能就是统计人口。

实现

1
2
3
4
5
6
7
8
package xyz.molzhao;

/**
* 角色Component:组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件
*/
public interface ICounter {
int count();
}
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
package xyz.molzhao;

import java.util.ArrayList;
import java.util.List;

/**
* 容器:容器对象,定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等
*/
public class Composite implements ICounter {
private List<ICounter> container = new ArrayList<>();

public boolean add(ICounter counter) {
return container.add(counter);
}

public boolean remove(ICounter counter) {
return container.remove(counter);
}

public List<ICounter> getChild() {
return container;
}

@Override
public int count() {
return container.stream().mapToInt(ICounter::count).sum();
}
}
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
package xyz.molzhao;

/**
* 叶子节点: 叶子节点。叶子节点没有子节点。
*/
public class City implements ICounter {

private Integer sum;

public City(Integer sum) {
this.sum = sum;
}

public Integer getSum() {
return sum;
}

public void setSum(Integer sum) {
this.sum = sum;
}

@Override
public int count() {
return sum;
}
}
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
import org.junit.Test;
import xyz.molzhao.City;
import xyz.molzhao.Composite;

import static org.junit.Assert.assertEquals;

/**
* 该案例实现一个统计的功能,主要统计整个中国的人口普查情况,需要各地方市统计好了,上报到各个省份,然后在上报给中央主要实现这样一个功能
*/
public class CompositeTest {
@Test
public void testCompositeTest() {
Composite china = new Composite();
// 新建浙江省容器
Composite zheJiang = new Composite();
// 嘉兴市统计完为1W人
City jiaxing = new City(10000);
City hangzhou = new City(20000);
zheJiang.add(jiaxing);
zheJiang.add(hangzhou);
china.add(zheJiang);

City shanghai = new City(30000);
china.add(shanghai);

/*
这边组成了
-中国
---浙江
-----嘉兴
-----杭州
---上海
这样的一个数据结构
*/
assertEquals(60000, china.count());
assertEquals(30000, shanghai.count());
assertEquals(30000, zheJiang.count());
assertEquals(10000, jiaxing.count());
}
}
1
2
3
4
5
// 最后结果
中国总人数为:60000
上海总人数为:30000
浙江总人数为:30000
嘉兴总人数为:10000

案例地址

欢迎大家访问我的Github地址,如果喜欢的话,希望能给个Star,点击此处获取本案例源码


如果有小伙伴,想要一起交流学习的,欢迎添加博主微信。

weChat