生活中存在很多 “部分-整体” 的关系,例如:大学中的学校与学院、学院与专业的关系。高楼与楼层和房间之间的关系等等。在软件开发中也有类似的情况。这些简单对象与复合对象之间的关系,如果用组合模式(把学校、院、系都看作是组织结构,他们之间没有继承的关系,而是一种树形结构,可以更好的实现管理操作)来实现会很方便。
一、基本介绍
1)、组合模式(Composite Pattern):又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
2)、组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
3)、这种设计模式属于结构型模式。
4)、组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象。
5)、优点:组合模式使得客户端代码可以一致地处理单个对象和组合对象,无需关系自己处理的是单个对象,还是组合对象,这简化了客户端代码;更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足 “开闭原则 OCP”。
6)、缺点:设计复杂,客户端需要花更多时间理清类之间的层次关系。不容易限制容器中构建,不容易用继承的方式增加构建的新功能。
二、组合模式——结构类图
组合模式分为透明式的组合模式和安全式组合模式:
1)、透明方式:在该方法中,由于抽象构建声明了所有子类中的全部方法,所以客户端无需区别树叶对象和树枝对象。对客户端来说是透明的。其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
2)、安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
三、组合模式代码案例分析
【1】抽象构件(Component)角色: 主要作用是为树叶和树枝构件生命公共接口,并实现他们的默认行为。在透明式的组合模式中,抽象构件还声明访问和管理子类的接口(add/remove)。在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构建完成。
public abstract class AbstractComponent {//学校的名称private String name;//备注private String remark;//定义一个输入的抽象方法public abstract void output();//非叶子节点都具有的增加和删除方法public void add(AbstractComponent abstractComponent) {//当重写此方法直接调用时,就会抛出此异常。与Arrays.asList()内部类中的add与remove写法一致throw new UnsupportedOperationException();}public void remove(AbstractComponent abstractComponent) {throw new UnsupportedOperationException();}//构造器public AbstractComponent(String name, String remark) {super();this.name = name;this.remark = remark;}//get/set方法 tostring方法 略}
}
【2】树枝构件(Composite)角色: 是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是
public class University extends AbstractComponent{//构造器public University(String name, String remark) {super(name, remark);}//大学中包含多个学院List<AbstractComponent> college = new ArrayList<AbstractComponent>();//重写输出方法:输出叶子节点@Overridepublic void output() {System.out.println("===="+super.getName()+"====");for (AbstractComponent abstractComponent : college) {abstractComponent.output();}}//组合类也就是树枝节点,需要重写add与remove方法@Overridepublic void add(AbstractComponent abstractComponent) {college.add(abstractComponent);}@Overridepublic void remove(AbstractComponent abstractComponent) {college.remove(abstractComponent);}
}
【3】学院类也是树枝构件(Composite)角色,与上述基本一致。
public class College extends AbstractComponent{//构造器public College(String name, String remark) {super(name, remark);}List<AbstractComponent> list = new ArrayList<>();//输入@Overridepublic void output() {for (AbstractComponent abstractComponent : list) {abstractComponent.output();}}//重写添加和删除方法@Overridepublic void add(AbstractComponent abstractComponent) {list.add(abstractComponent);}@Overridepublic void remove(AbstractComponent abstractComponent) {list.remove(abstractComponent);}
}
【4】树叶构件(Leaf)角色: 是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
public class Major extends AbstractComponent{//构造器public Major(String name, String remark) {super(name, remark);}//add , remove 就不用写了,因为他是叶子节点//输入@Overridepublic void output() {System.out.println(getName());}}
【5】测试类
public class Client {public static void main(String[] args) {//定义一个大学AbstractComponent university = new University("浙江大学", "浙江人的骄傲");//定义一个学院AbstractComponent college = new College("计算机学院", "简称妓院");//将妓院添加至学校university.add(college);//定义一个专业AbstractComponent major = new Major("计算机科学与技术", "考研大专业");//添加至学院college.add(major);//输出:计算机科学与技术 输入的都是叶子节点的output方法major.output();college.output();university.output();}
}
四、组合模式源码分析
【1】HashMap 组合模式:首先定义了抽象构建角色 Map<K,V>
public interface Map<K,V> {....}
【2】使用接口适配器模式,定义了抽象实现:AbstractMap
public abstract class AbstractMap<K,V> implements Map<K,V> {public V put(K key, V value) {//由子类实现throw new UnsupportedOperationException();}......
}
【3】HashMap
等实现类属于树枝构建角色:组合了Node
叶子节点类
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}public void putAll(Map<? extends K, ? extends V> m) {putMapEntries(m, true);}......final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;......}
}
【4】Node 类是上述类的内部类,也就是组合模式中的树叶构建:数组存储时,通过 put 方法存入 Node 对象中,叶子节点 Node 中不包含添加方法等,只包含一些属性和其 get/set 方法
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {......static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey() { return key; }public final V getValue() { return value; }public final String toString() { return key + "=" + value; }public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}......
}
五、组合模式的注意事项和细节
☛ 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
☛ 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做任何改动。
☛ 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容器添加节点或者叶子。从而创建出复杂的树形结构。
☛ 需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式。
☛ 当要求较高的抽象性时,如果节点和叶子有很多差异的话,例如很多方法和属性都不一样,不适合使用组合模式。