一、组合模式定义
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
如上图所示(截取自《Head First Design Patterns》一书),主要包括三个部分:
1. Component抽象组件。定义参加组合对象的共有方法和属性,可以定义一些默认的函数或属性。
2. Leaf叶子节点。构成组合树的最小构建单元。
3. Composite树枝节点组件。它的作用是组合树枝节点和叶子节点形成一个树形结构。
Component : 组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理 Component 的子部件。
1 abstract class Component { 2 protected String name; 3 4 public Component(String name) { 5 this.name = name; 6 } 7 8 public abstract void Add(Component c); 9 public abstract void Remove(Component c); 10 public abstract void Display(int depth); 11 }
Leaf : 表示叶节点对象。叶子节点没有子节点。
1 class Leaf extends Component { 2 3 public Leaf(String name) { 4 super(name); 5 } 6 7 @Override 8 public void Add(Component c) { 9 System.out.println("Can not add to a leaf"); 10 } 11 12 @Override 13 public void Remove(Component c) { 14 System.out.println("Can not remove from a leaf"); 15 } 16 17 @Override 18 public void Display(int depth) { 19 String temp = ""; 20 for (int i = 0; i < depth; i++) 21 temp += '-'; 22 System.out.println(temp + name); 23 } 24 25 }
Composite : 定义枝节点行为,用来存储子部件,在 Component 接口中实现与子部件相关的操作。例如 Add 和 Remove。
1 class Composite extends Component { 2 3 private List<Component> children = new ArrayList<Component>(); 4 5 public Composite(String name) { 6 super(name); 7 } 8 9 @Override 10 public void Add(Component c) { 11 children.add(c); 12 } 13 14 @Override 15 public void Remove(Component c) { 16 children.remove(c); 17 } 18 19 @Override 20 public void Display(int depth) { 21 String temp = ""; 22 for (int i = 0; i < depth; i++) 23 temp += '-'; 24 System.out.println(temp + name); 25 26 for (Component c : children) { 27 c.Display(depth + 2); 28 } 29 } 30 31 }
Client : 通过 Component 接口操作结构中的对象。
1 public class CompositePattern { 2 3 public static void main(String[] args) { 4 Composite root = new Composite("root"); 5 root.Add(new Leaf("Leaf A")); 6 root.Add(new Leaf("Leaf B")); 7 8 Composite compX = new Composite("Composite X"); 9 compX.Add(new Leaf("Leaf XA")); 10 compX.Add(new Leaf("Leaf XB")); 11 root.Add(compX); 12 13 Composite compXY = new Composite("Composite XY"); 14 compXY.Add(new Leaf("Leaf XYA")); 15 compXY.Add(new Leaf("Leaf XYB")); 16 compX.Add(compXY); 17 18 root.Display(1); 19 } 20 21 }
二、组合模式优势
节点自由扩展增加。使用组合模式,如果想增加一个树枝节点或者叶子节点都是很简单的,只要找到它的父节点就可以了,非常容易扩展,符合“开闭原则”。应用最广的模式之一。应用在维护和展示部分-整体关系的场景,如树形菜单、文件夹管理等等。一棵树形结构的所有节点都是Component,局部和整体对调用者来说都是一样的,没有区别,所以高层模块不比关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
1、可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
2、客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
3、定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
4、更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
组合模式的缺点: 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。
使用场景:
1、需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
2、让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
三、组合模式在Android源码中的应用
在Android源码中,都能找到使用组合模式的例子,其中在《Android源码学习之观察者模式应用》介绍到的ViewGroup和View的结构就是一个组合模式,结构图如下所示:
现在来看看它们是如何利用组合模式组织在一起的,首先在View类定义了有关具体操作,然后在ViewGroup类中继承View类,并添加相关的增加、删除和查找孩子View节点,代码如下:
/*
* @attr ref android.R.styleable#ViewGroup_clipChildren * @attr ref android.R.styleable#ViewGroup_clipToPadding * @attr ref android.R.styleable#ViewGroup_layoutAnimation * @attr ref android.R.styleable#ViewGroup_animationCache * @attr ref android.R.styleable#ViewGroup_persistentDrawingCache * @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache * @attr ref android.R.styleable#ViewGroup_addStatesFromChildren * @attr ref android.R.styleable#ViewGroup_descendantFocusability * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges */ public abstract class ViewGroup extends View implements ViewParent, ViewManager {
接着看增加孩子节点函数:
/*** Adds a child view. If no layout parameters are already set on the child, the* default parameters for this ViewGroup are set on the child.** @param child the child view to add** @see #generateDefaultLayoutParams()*/public void addView(View child) { addView(child, -1); } /** * Adds a child view. If no layout parameters are already set on the child, the * default parameters for this ViewGroup are set on the child. * * @param child the child view to add * @param index the position at which to add the child * * @see #generateDefaultLayoutParams() */ public void addView(View child, int index) { LayoutParams params = child.getLayoutParams(); if (params == null) { params = generateDefaultLayoutParams(); if (params == null) { throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); } } addView(child, index, params); } /** * Adds a child view with this ViewGroup's default layout parameters and the * specified width and height. * * @param child the child view to add */ public void addView(View child, int width, int height) { final LayoutParams params = generateDefaultLayoutParams(); params.width = width; params.height = height; addView(child, -1, params); } /** * Adds a child view with the specified layout parameters. * * @param child the child view to add * @param params the layout parameters to set on the child */ public void addView(View child, LayoutParams params) { addView(child, -1, params); } /** * Adds a child view with the specified layout parameters. * * @param child the child view to add * @param index the position at which to add the child * @param params the layout parameters to set on the child */ public void addView(View child, int index, LayoutParams params) { if (DBG) { System.out.println(this