组合模式
简介
组合模式(Composite Pattern)也称为整体-部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性,属于结构型模式。
组合关系与聚合关系的区别:
1、组合关系:也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在。部分对象与整体对象之间具有共生死的关系。(具有相同的生命周期)。
2、聚合关系:指的是整体与部分的关系。通常在定义一个整体类后,再去分析这个整体类的组成结构。从而找出一些组成类,该整体类和组成类之间就形成了聚合关系。例如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等(具有不同的生命周期)。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,最顶层的节点称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点。
由上图可以看出,其实根节点和树枝节点本质上是同一种数据类型。可以作为容器使用;而叶子节点与树枝节点在语义上不属于同一种类型,但是在组合模式中,会把树枝节点和叶子节点认为是同一种数据类型(用同一接口定义),让它们具备一致行为。这样,在组合模式中,整个树形结构中的对象u是同一种类型,带来的一个好处就是客户无需辨别树枝节点还是叶子节点,而是可以直接操作,给客户使用带来极大的便利。
组合模式的UML图:
组合模式包含3个角色:
1、抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性;
2、树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构;
3、叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次便利的最小单位。
组合模式在代码具体实现上,有两种不同的方式,分别是透明组合模式和安全组合模式。
组合模式的应用场景
当子系统与其内各个对象层次呈现树形结构时,可以使用组合模式让子系统内各个对象层次的行为操作具备一致性。客户端使用该子系统内任意一个层次对象时,无需进行区分,直接使用通用操作即可,为客户端的使用带来了便捷。
1、希望客户端可以忽略组合对象与单个对象的差异时;
2、对象层次具备整体和部分,呈树形结构。
生活中的树形菜单,操作系统目录结构,公司组织架构等。
透明组合模式
透明组合模式是把所有公共方法都定义在Component中,这样做的好处是客户端无需分辨是叶子节点(Leaf)还是树枝节点(Composite),它们具备完全一致的接口。上面的UML图就是透明模式。
透明组合模式把所有公共方法都定义在Component中,这样做的好处是客户端无需分辨是叶子节点(Leaf)和树枝节点(Composite),它们具备完全一致的接口;缺点是叶子节点(Leaf)会继承得到一些它所不需要(管理子类操作的方法)的方法,这与设计模式 接口隔离原则相违背。
安全组合模式
安全组合模式是只规定系统各个层次的最基础的一致行为,而把组合(树节点)本身的方法(管理子类对象的添加,删除等)放到自身当中。其中UML类图如下:
安全组合模式的好处是接口定义职责清晰,符合设计模式单一职责原则和接口隔离原则;缺点是客户需要区分树枝节点(Composite)和叶子节点(Leaf),这样才能正确处理各个层次的操作,客户端端无法依赖抽象(Component),违背了设计模式依赖倒置原则。
组合模式在源码中的应用
1、HashMap
中的putAll()
方法
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
这里可以看到putAll()方法传入的是Map对象,Map就是一个抽象构建(同时这个构建中支持键值对的存储格式),而HashMap是一个中间构建,HashMap中的Node节点就是叶子节点。中间构件就会有规定的存储方式。HashMap中的存储方式就是一个静态内部类的数组Node<K,V> tab
。
2、常用的ArrayList对象的addAll()
方法,其参数也是ArrayList的父类Collection
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
3、Mybatis解析各种Mapping文件的SQL,设计的类叫做SqlNode,xml中的每一个Node都会解析为SqlNode对象,最后把所有的SqlNode拼装到一起就形成一条完整的SQL语句。
public interface SqlNode {
boolean apply(DynamicContext context);
}
apply()方法会根据传入的参数context,参数解析该SqlNode所记录的SQL片段,并调用DynamicContenxt.appendSql()
方法将解析后的SQL片段追加到DynamicContenxt
的sqlBuilder中保存。当SQL节点下的所有SqlNode完成解析后,可以通过DynamicContenxt.getSql()
获取一条完成的SQL语句。
组合模式的优缺点
既然组合模式分为两种实现,那么肯定是不同的场合某一种会更加合适,也即具体情况具体分析。透明组合模式将公共接口封装到抽象根节点(Component)中,那么系统所有节点就具备一致性行为,所以如果当系统绝大多数层次具备相同的公共行为时,采用透明组合模式会更好一些。(代价:为剩下少数层次节点引入不需要的方法);而如果当前系统各个层次差异性行为较多或者树节点层次相对稳定(健壮)时,采用安全组合模式。
设计模式的理解应该是重于意而不是形,真正编码时,经常使用的是某种设计模式的变形体。真正切合项目的模式才是正确的设计模式。
优点:
1、清楚的定义分层次的复杂对象,表示对象的全部或部分层次
2、让客户端忽略了层次的差异,方便对整个层次结构进行控制
3、简化客户端代码
4、符合开闭原则
缺点:
1、限制类型时会较为复杂
2、使设计变得更加抽象