组合模式

1.简介

编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。

传统方法

将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系,因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。=>组合模式

基本介绍

  • 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。

  • 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。

  • 这种类型的设计模式属于结构型模式。

  • 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

    image-20210306204811423

Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件, Component可以是抽象类或者接口
Leaf :在组合中表示叶子节点,叶子节点没有子节点
Composite :非叶子节点,用于存储子部件,在Component接口中实现子部件的相关操作,比如增加(add),删除。

组合模式解决,当要处理的对象可以生成一颗树形的结果,我们要对树上的节点和叶子进行操作时,他能提供一致的方法

2.示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Data
@AllArgsConstructor
public abstract class OrganizationComponent {

private String name;//名字
private String des;//说明

protected void add(OrganizationComponent organizationComponent){
//默认实现
throw new UnsupportedOperationException();
}

protected void remove(OrganizationComponent organizationComponent){
//默认实现
throw new UnsupportedOperationException();
}

//做成抽象的,下面的子类都需要实现
protected abstract void print();
}

三种节点

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
//就是Composite,组合器,管理College
public class University extends OrganizationComponent {

List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();

//构造器
public University(String name, String des) {
super(name, des);
}

//重写add方法
@Override
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}

//重写remove方法
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}

//就是输出,包含学院
@Override
protected void print() {
System.out.println("-------"+getName()+"-------");
//遍历
for (OrganizationComponent organizationComponent:organizationComponents){
organizationComponent.print();
}
}
}
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
public class College extends OrganizationComponent {

//存放
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();

//构造器
public College(String name, String des) {
super(name, des);
}

//重写add方法
@Override
protected void add(OrganizationComponent organizationComponent) {
//实际业务中,添加方法不一定完全相同
organizationComponents.add(organizationComponent);
}

//重写remove方法
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}

//就是输出,包含专业
@Override
protected void print() {
System.out.println("-------"+getName()+"-------");
//遍历
for (OrganizationComponent organizationComponent:organizationComponents){
organizationComponent.print();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Department extends OrganizationComponent {

//构造器
public Department(String name, String des) {
super(name, des);
}
//add,remove不用写了,因为他是叶子节点
@Override
protected void print() {
System.out.println(getName());

}
}

测试

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
public class Client {
public static void main(String[] args) {
//从大到小创建对象,学校
University university = new University("清华大学", "中国顶级大学");

//学院
College college = new College("计算机学院", "计算机学院");
College college1 = new College("信息工程学院", "信息工程学院");

//创建各个专业
college.add(new Department("软件工程","软件工程"));
college.add(new Department("网络工程","网络工程"));

college1.add(new Department("通信工程","通信工程"));
college1.add(new Department("信息工程","信息工程"));

//将2个学院加入学校
university.add(college);
university.add(college1);

university.print();
}
}
/**
-------清华大学-------
-------计算机学院-------
软件工程
网络工程
-------信息工程学院-------
通信工程
信息工程
*/

3.在JDK中的使用

Java集合HashMap就使用了组合模式

HashMap 提供 putAll 的方法,可以将另一个 Map 对象放入自己的存储空间中,如果有相同的 key 值则会覆盖之前的 key 值所对应的 value 值

putAll 接收的参数为父类 Map 类型,所以 HashMap 是一个容器类,Map 的子类为叶子类,当然如果 Map 的其他子类也实现了 putAll 方法,那么它们都既是容器类,又都是叶子类

同理,ArrayList 中的 addAll(Collection<? extends E> c) 方法也是一个组合模式的应用,在此不做探讨

image-20210306213839803

Mybatis SqlNode中的组合模式

MyBatis 的强大特性之一便是它的动态SQL,其通过 if, choose, when, otherwise, trim, where, set, foreach 标签,可组合成非常灵活的SQL语句,从而提高开发人员的效率。

Mybatis在处理动态SQL节点时,应用到了组合设计模式,Mybatis会将映射配置文件中定义的动态SQL节点、文本节点等解析成对应的 SqlNode 实现,并形成树形结构。

4.注意事项

  • 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
  • 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
  • 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
  • 需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式.
  • 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

组合模式的主要缺点如下:

  • 使得设计更加复杂,客户端需要花更多时间理清类之间的层次关系。
  • 在增加新构件时很难对容器中的构件类型进行限制。

适用场景

  • 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构。
  • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。