什么是组合模式?
组合模式是一种对象结构型模式。
组合模式是组合多个对象形成树形结构以表示具有整体——部分关系的层次结构,组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性,所以也可称之为整体——部分(Part-Whole)模式。
组合模式在日常coding中也很常见,比如在笔者在后台设计中,遇到过这样的问题,左侧导航栏根据用户权限进行动态修改。左侧导航栏纵深可能会无限大,相当于拥有无限高度的树结构。如果按照常规的写法,我们需要不断进行嵌套循环遍历,如果树的高度是2,那么这个算法写起来可能会比较容易,但是如果树的高度是8,我相信你已经要摔键盘了。但是组合模式可以很方便的解决这个问题,我们可以将菜单按钮注册到菜单中,当菜单进行改变后,调用菜单声明的方法,注册者也会收到通知。
组合模式关键点是定义了一个统一的抽象构建类,它既可以代表叶子对象,保证了对外部调用的一致性,外部调用方便进行统一处理。
核心结构
- Component。抽象构件,主要负责为叶子构件和容器构件声明公共方法,同时还可以声明为容器添加、获取和删除叶子构件的方法,所以它基本上声明和实现了子类中共有的方法,它既可以是接口,也可以是抽象类。
- Leaf。叶子构件,表现为叶子对象,它没有子节点,它实现了抽象构件中声明的业务实现,对于那些容器管理方法,可以通过逻辑或者异常进行处理。
- Composite。容器构件,表现为容器对象,它提供一个集合对象用于存储子节点,对于容器中的子节点,可以通过递归或者迭代调用其业务实现,同时自身也实现了抽象构件中声明的业务实现。
类图展示
组合模式又分为透明组合模式和安全组合模式,我们分别对两中情况做逐一的介绍。
透明组合模式
在透明组合模式中,Component(抽象构件)声明了所有管理对象的方法,但是叶子构件不会拥有子节点,这些方法对叶子构件来说是理应不存在的,所以在每声明一个叶子节点时,都需要对这些方法进行适当的处理。但是在外部调用看来,叶子构件和容器构件所提供的方法是一致的,也就说,对于外部调用来说,是透明的。
根据透明组合模式的定义,我们可以构建透明组合模式的类图。
安全组合模式
安全组合模式相对于透明组合模式来说更加安全,因为Component(抽象构件)中没有声明任何用于管理对象的方法,而是在Composite(容器构件)中声明并实现这些方法,所以Leaf(叶子构件)中不会存在这些不需要的方法,这种做法是安全的,因为客户端永远不会调用到不适合的方法。
根据安全组合模式的定义,我们可以构建安全组合模式的类图。
代码分析
在等级分明的企业中,经常会有大大小小的各种部门,各个部门又拥有数量不一的员工,当需要事件通知时,将这种等级架构设计为组合模式可以使行为更加便捷,我们根据两种组合模式分别给出不同的架构设计。
透明组合模式实现
首先,我们需要定义一个抽象构件,也就是通知管理系统:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
* 组合模式——透明组合模式——抽象构件
*/
public abstract class Notification {
public abstract void notification();
public abstract Notification getChild(int i);
public abstract void add(Notification notification);
public abstract void remove(Notification notification);
}
接着我们定义叶子构件,也就是员工:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
* 组合模式——透明组合模式——叶子构件
*/
public class Ant extends Notification {
private String name;
public Ant(String name) {
this.name = name;
}
@Override
public void notification() {
System.out.println("notifying employees".concat(this.name).concat(" in transparency"));
}
@Override
public Notification getChild(int i) {
System.out.println("invalid");
return null;
}
@Override
public void add(Notification notification) {
System.out.println("invalid");
}
@Override
public void remove(Notification notification) {
System.out.println("invalid");
}
}
接着我们定义一个容器构件,也就是部门:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
* 组合模式——透明组合模式——容器构件
*/
public class Department extends Notification {
private String name;
private List<Notification> antList = new ArrayList<Notification>();
public Department(String name) {
this.name = name;
}
@Override
public void notification() {
System.out.println("Notifying each employees of Department ".concat(this.name).concat(" in transparency"));
for(Notification notification : antList) {
notification.notification();
}
}
@Override
public Notification getChild(int i) {
return antList.get(i);
}
@Override
public void add(Notification notification) {
antList.add(notification);
}
@Override
public void remove(Notification notification) {
antList.remove(notification);
}
}
安全组合模式
跟透明组合模式一样,定义通知管理系统:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
* 组合模式——安全组合模式——抽象构件
*/
public abstract class Notification {
public abstract void notification();
}
定义员工:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
* 组合模式——安全组合模式——叶子构件
*/
public class Ant extends Notification {
private String name;
public Ant(String name) {
this.name = name;
}
@Override
public void notification() {
System.out.println("has notified ".concat(this.name).concat(" safe"));
}
}
定义部门:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
* 组合模式——安全组合模式——容器构件
*/
public class Department extends Notification {
private String name;
private List<Notification> antList;
public Department(String name) {
this.name = name;
antList = new ArrayList<Notification>();
}
@Override
public void notification() {
System.out.println("Notifying each employee of Department ".concat(this.name).concat(" safe"));
for (Notification notification : antList) {
notification.notification();
}
}
public void add(Notification notification) {
antList.add(notification);
}
public void remove(Notification notification) {
antList.remove(notification);
}
public Notification getChild(int i) {
return antList.get(i);
}
}
测试用例
为了能形成对比,笔者将测试用例写在了一起。
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/19 0019.
* 组合模式测试
*/
public class CombineTest {
public static void main(String[] args) {
transparentCombineTest();
safeCombineTest();
}
private static void transparentCombineTest() {
try {
Notification employeeOne = new Ant("sunshine_6324");
Notification employeeTwo = new Ant("sunshine_443");
Notification employeeThree = new Ant("sunshine_4396");
Notification department = new Department("Director");
department.add(employeeOne);
department.add(employeeTwo);
department.notification();
employeeThree.notification();
department.add(employeeThree);
department.notification();
department.remove(employeeOne);
department.notification();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void safeCombineTest() {
try {
com.sunshine.patterns.combine.safety.Ant employeeOne = new com.sunshine.patterns.combine.safety.Ant("sunshine_6324");
com.sunshine.patterns.combine.safety.Ant employeeTwo = new com.sunshine.patterns.combine.safety.Ant("sunshine_443");
com.sunshine.patterns.combine.safety.Ant employeeThree = new com.sunshine.patterns.combine.safety.Ant("sunshine_4396");
com.sunshine.patterns.combine.safety.Department department = new com.sunshine.patterns.combine.safety.Department("Director");
department.add(employeeOne);
department.add(employeeTwo);
department.notification();
employeeThree.notification();
department.add(employeeThree);
department.notification();
department.remove(employeeOne);
department.notification();
} catch (Exception e) {
e.printStackTrace();
}
}
}
模式总结
优点
- 组合模式可以清楚的定义分层次的复杂对象,让外部调用忽略层次的差异,方便对整个层次结构进行控制。
- 外部调用不必在意处理的对象是容器对象还是单个对象,简化了代码。
- 符合开闭原则,扩展容器构件和叶子构件时,只需要实现抽象构件即可。
- 组合模式为树形结构的面向对象编程提供了一种灵活的解决方案,通过容器构件和叶子构件的递归组合,可以形成复杂的树形结构,但是对树形结构的控制确非常简单。
缺点
- 组合模式很难对容器构件中,叶子构件的类型进行限制,因为它们都来自于同一个抽象层,在一定程度上增加了系统的复杂性和逻辑性。
适用场景
- 在群体和单个对象的层次中,希望用一种方式忽略群体和单个对象之间的差异。
- 树形结构的面向对象编程。
- 系统中容器构件和叶子对象很明确,并且需要对容器构件和叶子构件进行扩充。