组合模式
组合模式就是在有多个层级中,整体和部分之间的关系中,如果用继承的话会很臃肿和有些子类并不需要父类的全部功能
子类的最终使用场景一般都是在具有多个层级下,大概的功能方法趋于相同的情况可以使用。
看了如下文章相信你会有一个新的理解
整体与部分的关系
很多场景下,需要考虑整体和部分之间的关系,在对其进行管理功能设计时,需要考虑到灵活性和扩展性,而继承关系往往不能很好地解决这些问题。一个最典型的例子就是:组织机构。比如,就公司而言,总公司可能下设有分公司,分公司下又有办事处,而办事处、分公司、总公司可能都会存在一些职能部门。很明显,组织机构是一个整体和部分的关系,并且它是一颗树状结构。

设计这样的结构,你是否会使用继承关系?如让分公司继承总公司、办事处继承分公司,这样往往是从机构的大小维度来划分的,子类除了具备父类的功能外,还能够独立扩展功能。但是,在组织机构树中,其实每个节点(机构)所承担只能都是差不多的,换言之,总公司、分公司、办事处三者其实并没有父子关系,在管理职能上他们的功能是相同的,比如都能添加、删除、查询子的机构节点。所以采用继承关系来设计其实是不合适的。
还有很多整体-部分关系的例子,比如学校、学院和系的关系,电脑组装商家可以出售整机也可以出售配件,文字处理软件中单个文字和整段甚至整篇文字的处理方式差不多,又如Java AWT、Swing中的容器组件和简单控件的关系。其实,这一类问题,最终解决的都是整体和部分被一致对待的问题,组合模式很好的解决了这个问题。
组合模式简介
在Design Patterns一书中对组合模式的定义如下:
组合模式(Composite Pattern),将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式是一种结构型设计模式,其”对象组合成树形结构”定义代表了对整体部分关系的抽象,将树形结构抽象为三个部分:根节点、树枝节点和叶子节点,其中,根节点、树枝节点可以添加子节点,而叶子节点不能添加子节点。因此,组合模式有两种方式:透明方式和安全方式。
在说明这两种方式之前,先看看组合模式的角色:
- 抽象根节点(Component):对象组合的高度抽象构件,声明了对象操作的公共接口。透明方式和安全方式对节点管理方法的声明存在差异:透明方式会声明管理节点的接口,如添加、删除,但是安全方式不会声明管理接口,而是将其移交到树枝节点声明和实现,具体后边再说;
- 树枝节点(Composite):树形结构的分支节点,实现抽象构件,同时承担管理子节点职能,如实现添加、删除节点的方法;
- 树叶节点(Leaf):
树形结构的叶子节点,没有子节点,是系统层次遍历的最小单位,所以不承担管理职能,它实现添加、删除等管理职能方法是没有意义的;
透明方式和安全方式主要区别在于:客户端是否需要区分树枝构件(Composite)和树叶构件(Leaf),透明方式则不区分,而安全方式则需要区分。
透明方式
透明方式,不区分树叶构件和树枝构件,两者都实现构建构件的api,因此,树形结构管理职能的API(添加、修改、删除等)声明在抽象构件中,客户端使用时不需要区分树枝和树叶,因为他们具有相同的方法。类图如下:

这种方式的好处在于,客户端不需要判断树叶构件和树枝构件,因此对客户端是统一的或者说透明的,方便管理;但是前边我们说过,树叶构件是没有子节点的,它不承担管理职能,实现管理职能的方法没有意思,因此我们只能进行方法空实现或者抛出异常,这一点不太友好。
安全方式
相对于透明方式,安全方式显得比较保守。它只在抽象构件中声明公共方法,管理职能的方法放到树枝构件中进行声明和实现,这样树叶构件就不会存在管理职能的方法了。类图如下:

这种方式的优点在于,树叶构件不需要对管理职能方法做无意义的空实现或抛异常,代码更严谨,但是其缺点也很明显:客户端需要明确知道调用的是树叶构件还是树枝构件,相对而言比较麻烦。
应用示例
看一个例子。大学中分为多个学院,每个学院下又存在多个系(专业),我们看看如果运用组合模式来实现。
分析:学校、学院、系属于整体和部分关系,我们将其看做同一个整体并进行抽象,得到一个组织机构接口,让他们分别实现该接口。同时,学校、学院下都包含子节点,因此他们可以看做树枝构件,内部持有一个List来保存子节点数据,而系下不再有子节点,它属于树叶构件。

类图中,Organization为抽象构件,声明了add、remove两个管理职能方法,而业务处理方法只有一个print方法,用来打印自身和其下的所有子节点。
从类图可以看出,我们使用的是组合模式的透明方式。
具体代码如下:
1、抽象构件:
1 2 3 4 5 6 7
| interface Organization { void add(Organization org);
void remove(Organization org);
void print(); }
|
2、树枝构件:
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
| class University implements Organization { private final String name; private final List<Organization> colleges = new ArrayList<>();
public University(String name) { this.name = name; }
@Override public void add(Organization org) { this.colleges.add(org); }
@Override public void remove(Organization org) { this.colleges.remove(org); }
@Override public void print() { System.out.println(this.name); for (Organization college : colleges) { college.print(); } } }
|
- 用一个
List结构存储其子节点信息。 - 先打印自身,然后在调用每一个子节点的
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
| class College implements Organization { private final String name; private final List<Organization> depts = new ArrayList<>();
public College(String name) { this.name = name; }
@Override public void add(Organization org) { this.depts.add(org); }
@Override public void remove(Organization org) { this.depts.remove(org); }
@Override public void print() { System.out.println("--" + this.name); for (Organization dept : depts) { dept.print(); } } }
|
3、树叶构件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Department implements Organization { private final String name;
public Department(String name) { this.name = name; }
@Override public void add(Organization org) { throw new UnsupportedOperationException("系不能进行添加操作"); }
@Override public void remove(Organization org) { throw new UnsupportedOperationException("系不能进行删除操作"); }
@Override public void print() { System.out.println("----" + this.name); } }
|
- 对于管理方法,直接抛异常
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| public abstract class MenuComponent { String name; Integer level; public void add(MenuComponent menuComponent) { throw new UnsupportedOperationException("不支持添加操作!"); } public void remove(MenuComponent menuComponent) { throw new UnsupportedOperationException("不支持删除操作!"); } public MenuComponent getChild(Integer i) { throw new UnsupportedOperationException("不支持获取子菜单操作!"); } public String getName() { throw new UnsupportedOperationException("不支持获取名字操作!"); } public void print() { throw new UnsupportedOperationException("不支持打印操作!"); } }
public class Menu extends MenuComponent { private List<MenuComponent> menuComponentList = new ArrayList<>(); public Menu(String name,int level){ this.level = level; this.name = name; } @Override public void add(MenuComponent menuComponent) { menuComponentList.add(menuComponent); } @Override public void remove(MenuComponent menuComponent) { menuComponentList.remove(menuComponent); } @Override public MenuComponent getChild(Integer i) { return menuComponentList.get(i); } @Override public void print() { for (int i = 1; i < level; i++) { System.out.print("--"); } System.out.println(name); for (MenuComponent menuComponent : menuComponentList) { menuComponent.print(); } } }
public class MenuItem extends MenuComponent { public MenuItem(String name,int level) { this.name = name; this.level = level; } @Override public void print() { for (int i = 1; i < level; i++) { System.out.print("--"); } System.out.println(name); } } public static void main(String[] args) { MenuComponent component = new Menu("系统管理",1); MenuComponent menu1 = new Menu("用户管理",2); menu1.add(new MenuItem("新增用户",3)); menu1.add(new MenuItem("修改用户",3)); menu1.add(new MenuItem("删除用户",3)); MenuComponent menu2 = new Menu("角色管理",2); menu2.add(new MenuItem("新增角色",3)); menu2.add(new MenuItem("修改角色",3)); menu2.add(new MenuItem("删除角色",3)); menu2.add(new MenuItem("绑定用户",3)); component.add(menu1); component.add(menu2); component.print(); }
系统管理 --用户管理 ----新增用户 ----修改用户 ----删除用户 --角色管理 ----新增角色 ----修改角色 ----删除角色 ----绑定用户
|
总结
适用场景:
- 希望客户端可以忽略组合对象与单个对象的差异时。
- 对象层次具备整体和部分,呈树形结构(如树形菜单,操作系统目录结构,公司组织架构等)。
优点:
- 清楚地定义分层次的复杂对象,表示对象的全部或部分层次。
- 让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 简化客户端代码。
- 符合开闭原则。
缺点:
参考文章:Java设计模式(12)-组合模式