基本介绍
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
实例介绍
对于一个大学,一个大学有许多学院,学院里又有着不同的专业,想要得到不同层次的信息,例如得到学校的信息需要打印出学校名称、所有学院名称、各个学院的各个专业名称,需要打印出类似下列信息:
---------某某大学---------
---------计算机与软件学院---------
软件工程
计算机科学与技术
---------电子与信息学院---------
通信工程
电子科学与技术
同时,我如果仅仅需要某一个学院的信息,只从中取出学院信息进行打印。传统的方法是定义学校为抽象父类,学院为抽象子类,专业为具体节点子类,但是在继承的过程中类的信息不断扩大,类的信息就会变成下图:
我们开始的目的是得到学校包含学院,学院包含专业:
我们可以用组合模式来解决这个问题
UML类图
Component是抽象父类,定义了需要的方法与属性作为标准,下面每个阶段的类都继承这个父类,同时聚合了这个父类,最小的一层为叶子节点。
具体实现
抽象父类Component,预先实现了add(),remove()方法,叶子节点不需重写add(),remove()方法,还定义了一个抽象方法print()输出信息。
public abstract class Component {
public String name;
protected void add(Component component){
System.out.println("子节点不能执行添加");
}
protected void remove(Component component){
System.out.println("子节点不能执行删除");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Component(String name) {
this.name = name;
}
protected abstract void print();
}
树的第一层为根节点University,用List来接收其子节点,集成抽象父类重写了各个方法。
public class University extends Component {
List<Component> components = new ArrayList<>();
public University(String name) {
super(name);
}
@Override
protected void add(Component component) {
components.add(component);
}
@Override
protected void remove(Component component) {
components.remove(component);
}
@Override
public String getName() {
return super.getName();
}
@Override
protected void print() {
System.out.println("---------"+getName()+"---------");
//遍历的是当前节点的子节点
//也就是当前为学校节点的子节点学院
for (Component component : components) {
component.print();
}
}
}
第二层College的实现跟University差不多:
public class College extends Component {
List<Component> components = new ArrayList<>();
public College(String name) {
super(name);
}
@Override
protected void add(Component component) {
components.add(component);
}
@Override
protected void remove(Component component) {
components.remove(component);
}
@Override
public String getName() {
return super.getName();
}
@Override
protected void print() {
System.out.println("---------"+getName()+"---------");
for (Component component : components) {
component.print();
}
}
}
叶子节点(没有子节点)的实现,仅仅重写了print()方法和getName()方法,如果子节点调用add方法的话会使用父类预先实现的方法。
public class Major extends Component {
public Major(String name) {
super(name);
}
@Override
public String getName() {
return super.getName();
}
@Override
protected void print() {
System.out.println(getName());
}
}
客户端调用
public class Client {
public static void main(String[] args) {
//新建学校、两个学院的实例
Component university = new University("某某大学");
Component cs = new College("计算机与软件学院");
Component ee = new College("电子与信息学院");
//将Major对象加进每个学院
cs.add(new Major("软件工程"));
cs.add(new Major("计算机科学与技术"));
ee.add(new Major("通信工程"));
ee.add(new Major("电子科学与技术"));
//将College对象加进学校
university.add(cs);
university.add(ee);
university.print();
System.out.println("↓↓↓↓↓↓仅仅测试计算机学院↓↓↓↓↓↓");
cs.print();
System.out.println("↓↓↓↓↓↓电信学院↓↓↓↓↓↓");
ee.print();
}
}
输出结果:
---------某某大学---------
---------计算机与软件学院---------
---软件工程
---计算机科学与技术
---------电子与信息学院---------
---通信工程
---电子科学与技术
↓↓↓↓↓↓仅仅测试计算机学院↓↓↓↓↓↓
---------计算机与软件学院---------
---软件工程
---计算机科学与技术
↓↓↓↓↓↓电信学院↓↓↓↓↓↓
---------电子与信息学院---------
---通信工程
---电子科学与技术
总结
- 解决问题:客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。适合用于遍历组织结构,或具备树形结构的情形。
- 如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
- 优点:较强的扩展性。
- 缺点:违反了依赖倒置原则。