1. 组合模式概述
概念
- 又叫整体模式,是用于把一组相似的对象当作一个单一的对象。虽然整体包含了部分,但无论整体或部分,都具有一致的行为
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构
特点
- 桥接模式用于将同等级的接口互相组合;组合模式用于整体与部分的结构
- 当整体与部分有相似的结构,在操作时可以被一致对待时,就可以使用组合模式。例如
- 大树枝和分树枝的关系:树枝可以长出叶子、也可以长出树枝,分树枝也一样
- 总公司和子公司的关系:总公司可以设立部门、也可以设立分公司,子公司也一样
- 文件夹和子文件夹的关系:文件夹中可以存放文件、也可以新建文件夹,子文件夹也一样
2. 组合模式 Demo
不使用组合模式的设计方案
新建管理者类
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
41
42
43
44
45
46
47public class Manager {
// 职位
private String position;
// 工作内容
private String job;
// 管理的管理者
private List<Manager> managers = new ArrayList<>();
// 管理的职员
private List<Employee> employees = new ArrayList<>();
public Manager(String position, String job) {
this.position = position;
this.job = job;
}
public void addManager(Manager manager) {
managers.add(manager);
}
public void removeManager(Manager manager) {
managers.remove(manager);
}
public void addEmployee(Employee employee) {
employees.add(employee);
}
public void removeEmployee(Employee employee) {
employees.remove(employee);
}
// 做自己的本职工作
public void work() {
System.out.println("我是" + position + ",我正在" + job);
}
// 检查下属
public void check() {
work();
for (Employee employee : employees) {
employee.work();
}
for (Manager manager : managers) {
manager.check();
}
}
}新建职员类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Employee {
// 职位
private String position;
// 工作内容
private String job;
public Employee(String position, String job) {
this.position = position;
this.job = job;
}
// 做自己的本职工作
public void work() {
System.out.println("我是" + position + ",我正在" + job);
}
}客户端建立人员结构关系
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
29public class Client {
@Test
public void test() {
Manager boss = new Manager("老板", "唱怒放的生命");
Employee HR = new Employee("人力资源", "聊微信");
Manager PM = new Manager("产品经理", "不知道干啥");
Manager CFO = new Manager("财务主管", "看剧");
Manager CTO = new Manager("技术主管", "划水");
Employee UI = new Employee("设计师", "画画");
Employee operator = new Employee("运营人员", "兼职客服");
Employee webProgrammer = new Employee("程序员", "学习设计模式");
Employee backgroundProgrammer = new Employee("后台程序员", "CRUD");
Employee accountant = new Employee("会计", "背九九乘法表");
Employee clerk = new Employee("文员", "给老板递麦克风");
boss.addEmployee(HR);
boss.addManager(PM);
boss.addManager(CFO);
PM.addEmployee(UI);
PM.addManager(CTO);
PM.addEmployee(operator);
CTO.addEmployee(webProgrammer);
CTO.addEmployee(backgroundProgrammer);
CFO.addEmployee(accountant);
CFO.addEmployee(clerk);
boss.check();
}
}运行测试方法,输出如下
1
2
3
4
5
6
7
8
9
10
11我是老板,我正在唱怒放的生命
我是人力资源,我正在聊微信
我是产品经理,我正在不知道干啥
我是设计师,我正在画画
我是运营人员,我正在兼职客服
我是技术主管,我正在划水
我是程序员,我正在学习设计模式
我是后台程序员,我正在CRUD
我是财务主管,我正在看剧
我是会计,我正在背九九乘法表
我是文员,我正在给老板递麦克风这样设计的公司结构有两个弊端:name 字段、job 字段、work 方法重复了,可以将重复的字段和方法提取到一个工具类中,这样可以消除重复,但逻辑上并不利于程序的高内聚;管理者对其管理的管理者和职员需要区别对待,此方案无法解决,此方案中 Employee 和 Manager 类完全是两个不同的对象,两者的相似性被忽略了
透明方式的组合模式
新建一个抽象的组件类(组合模式最主要的功能就是让用户可以一致对待整体和部分结构,将两者都作为一个相同的组件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public abstract class Component {
// 职位
private String position;
// 工作内容
private String job;
public Component(String position, String job) {
this.position = position;
this.job = job;
}
// 做自己的本职工作
public void work() {
System.out.println("我是" + position + ",我正在" + job);
}
abstract void addComponent(Component component);
abstract void removeComponent(Component component);
abstract void check();
}管理者继承自该抽象类
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
27public class Manager extends Component {
// 管理的组件
private List<Component> components = new ArrayList<>();
public Manager(String position, String job) {
super(position, job);
}
public void addComponent(Component component) {
components.add(component);
}
void removeComponent(Component component) {
components.remove(component);
}
// 检查下属
public void check() {
work();
for (Component component : components) {
component.check();
}
}
}职员同样继承该抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class Employee extends Component {
public Employee(String position, String job) {
super(position, job);
}
@Override
void addComponent(Component component) {
System.out.println("职员没有管理权限");
}
@Override
void removeComponent(Component component) {
System.out.println("职员没有管理权限");
}
@Override
void check() {
work();
}
}修改客户端如下
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
29public class Client {
@Test
public void test(){
Component boss = new Manager("老板", "唱怒放的生命");
Component HR = new Employee("人力资源", "聊微信");
Component PM = new Manager("产品经理", "不知道干啥");
Component CFO = new Manager("财务主管", "看剧");
Component CTO = new Manager("技术主管", "划水");
Component UI = new Employee("设计师", "画画");
Component operator = new Employee("运营人员", "兼职客服");
Component webProgrammer = new Employee("程序员", "学习设计模式");
Component backgroundProgrammer = new Employee("后台程序员", "CRUD");
Component accountant = new Employee("会计", "背九九乘法表");
Component clerk = new Employee("文员", "给老板递麦克风");
boss.addComponent(HR);
boss.addComponent(PM);
boss.addComponent(CFO);
PM.addComponent(UI);
PM.addComponent(CTO);
PM.addComponent(operator);
CTO.addComponent(webProgrammer);
CTO.addComponent(backgroundProgrammer);
CFO.addComponent(accountant);
CFO.addComponent(clerk);
boss.check();
}
}分析
- 使用组合模式,解决了之前的两个弊端,一是将共有的字段与方法移到了父类中、消除了重复;二是在客户端中,可以一致对待 Manager 和 Employee 类。即:Manager 类和 Employee 类统一声明为 Component 对象;统一调用 Component 对象的
addComponent()
方法添加子对象即可 - Employee 类虽然继承了父类的
addComponent()
方法和removeComponent()
方法,但仅仅提供了一个空实现,因为 Employee 类是不支持添加和移除组件的。违背了接口隔离原则:客户端不应依赖它不需要的接口,如果一个接口在实现时,部分方法由于冗余被客户端空实现,则应该将接口拆分,让实现类只需依赖自己需要的接口方法 - 这种违背了接口隔离原则的组合模式称为透明方式:在 Component 中声明所有管理子对象的方法,包括
add()
、remove()
等,这样继承自 Component 的子类都具备了add()
、remove()
方法。对于外界来说叶节点和枝节点是透明的,它们具备完全一致的接口- 透明方式组合模式的优点:让 Manager 类和 Employee 类具备完全一致的行为接口,调用者可以一致对待它们
- 透明方式组合模式的缺点:Employee 类并不支持管理子对象,不仅违背了接口隔离原则,而且客户端可以用 Employee 类调用
addComponent()
和removeComponent()
方法,导致程序出错,所以这种方法是不安全的
- 使用组合模式,解决了之前的两个弊端,一是将共有的字段与方法移到了父类中、消除了重复;二是在客户端中,可以一致对待 Manager 和 Employee 类。即:Manager 类和 Employee 类统一声明为 Component 对象;统一调用 Component 对象的
安全方式的组合模式
修改抽象类:在父类中去掉了
addComponent()
和removeComponent()
这两个抽象方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public abstract class Component {
// 职位
private String position;
// 工作内容
private String job;
public Component(String position, String job) {
this.position = position;
this.job = job;
}
// 做自己的本职工作
public void work() {
System.out.println("我是" + position + ",我正在" + job);
}
abstract void check();
}修改 Manager 类:Manager 单独实现了
addComponent()
和removeComponent()
这两个方法,去掉了 @Override 注释1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class Manager extends Component {
// 管理的组件
private List<Component> components = new ArrayList<>();
public Manager(String position, String job) {
super(position, job);
}
public void addComponent(Component component) {
components.add(component);
}
void removeComponent(Component component) {
components.remove(component);
}
// 检查下属
@Override
public void check() {
work();
for (Component component : components) {
component.check();
}
}
}修改 Employee 类:
1
2
3
4
5
6
7
8
9
10
11public class Employee extends Component {
public Employee(String position, String job) {
super(position, job);
}
void check() {
work();
}
}客户端建立人员结构关系
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
29public class Client {
@Test
public void test(){
Manager boss = new Manager("老板", "唱怒放的生命");
Employee HR = new Employee("人力资源", "聊微信");
Manager PM = new Manager("产品经理", "不知道干啥");
Manager CFO = new Manager("财务主管", "看剧");
Manager CTO = new Manager("技术主管", "划水");
Employee UI = new Employee("设计师", "画画");
Employee operator = new Employee("运营人员", "兼职客服");
Employee webProgrammer = new Employee("程序员", "学习设计模式");
Employee backgroundProgrammer = new Employee("后台程序员", "CRUD");
Employee accountant = new Employee("会计", "背九九乘法表");
Employee clerk = new Employee("文员", "给老板递麦克风");
boss.addComponent(HR);
boss.addComponent(PM);
boss.addComponent(CFO);
PM.addComponent(UI);
PM.addComponent(CTO);
PM.addComponent(operator);
CTO.addComponent(webProgrammer);
CTO.addComponent(backgroundProgrammer);
CFO.addComponent(accountant);
CFO.addComponent(clerk);
boss.check();
}
}分析
安全方式:在 Component 中不声明 add 和 remove 等管理子对象的方法,这样叶节点就无需实现它,只需在枝节点中实现管理子对象的方法即可
- 安全方式的组合模式优点:安全方式遵循了接口隔离原则
- 安全方式的组合模式缺点:安全方式由于不够透明,Manager 和 Employee 不具有相同的接口,客户端无法将 Manager 和 Employee 统一声明为 Component,必须要区别对待,带来了使用上的不方便
透明方式和安全方式各有好处,在使用组合模式时,需要根据实际情况决定。大多数使用组合模式的场景都是采用透明方式,虽然有点不安全,但是客户端无需做任何判断来区分是叶子结点还是枝节点,这一点很方便
组合模式中的透明方式与安全方式的区别
- 透明方式:在 Component 中声明所有管理子对象的方法,包括 add、remove 等,这样继承自 Component 的子类都具备了 add、remove 方法。对于外界来说叶节点和枝节点是透明的,它们具备完全一致的接口
- 安全方式:在 Component 中不声明 add 和 remove 等管理子对象的方法,这样叶节点就无需实现它,只需在枝节点中实现管理子对象的方法即可