一、简述
组合模式(Composite Pattern),也称作部分整体模式(Part-Whole Pattern),将一组相似的对象看做一个对象处理,并根据一个树状结构来组合对象;对象都提供一个统一的方法去访问相应的对象来处理多个对象的同一性问题。
组合模式属于结构设计模式之一,而其设计目的就是将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性;所以决定组合模式的设计基础就是树状结构,组合模式所适用的情况也就是树状结构或者适合适用树状结构来解决问题的情况。
- Component:抽象节点,为组合中的对象声明统一接口
- Composite:可以储存子节点的节点对象,并实现抽象节点的有关操作
- Leaf:叶子节点,没有子节点对象
- Client:组合节点对象,进行操作
这里所描述的是透明的组合模式,可以看到Component
类中除了统一的操作方法doSomthing()
方法以外,还有操作子节点的相关方法,而叶子节点Leaf
类定义就是没有叶子节点的,显然这些操作子节点的方法就是多余的。
如果要让Leaf
类不继承这些方法,只能将Component
类中的这些方法放到它的子类Composite
中;然而这样的设计方式与依赖倒置原则相违背,所以这里并没有采用这种组合模式,即安全的组合模式。
二、实现
说到组合模式,最适合的就是文件系统的结构了,文件夹中就子文件夹和文件,子文件夹中可能又是如此,典型的树状结构。
抽象的目录类,有目录名,有输出目录名,并提供添加目录、删除目录以及清空目录的的功能方法
public abstract class Directory {
//当前目录名
private final String name;
public Directory(String name) {
this.name = name;
}
/**
* 输出目录结构
*/
public abstract void print();
/**
* 添加一个文件或者文件夹
* @param dir
*/
public abstract void addDir(Directory dir);
/**
* 删除一个文件或者文件夹
* @param dir
*/
public abstract void removeDir(Directory dir);
/**
* 清空目录
*/
public abstract void clear();
/**
* 获取目录中的所有目录
* @return
*/
public abstract List getDirectories();
/**
* 获取目录名
* @return
*/
public String getName() {
return name;
}
}
文件夹类,申明一个集合储存自身所报的目录,实现了具体的目录操作方法,在print()
方法中循环调用集合中目录的print()
方法输出
public class Folder extends Directory {
/**
* 当前文件夹下的所有目录元素
*/
protected List directories = new ArrayList<>();
public Folder(String name) {
super(name);
}
@Override
public void print() {
System.out.print(getName() + "[");
Iterator iterator = directories.iterator();
while (iterator.hasNext()){
Directory directory = iterator.next();
directory.print();
if(iterator.hasNext()){
System.out.print(", ");
}
}
System.out.print("]");
}
@Override
public void addDir(Directory dir) {
directories.add(dir);
}
@Override
public void removeDir(Directory dir) {
directories.remove(dir);
}
@Override
public void clear() {
directories.clear();
}
@Override
public List getDirectories() {
return directories;
}
}
文件类,实现了print()
方法,由于没有子目录,相关操作的方法都抛出异常
public class File extends Directory {
public File(String name) {
super(name);
}
@Override
public void print() {
System.out.print(getName());
}
@Override
public void addDir(Directory dir) {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public void removeDir(Directory dir) {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public void clear() {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public List getDirectories() {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
}
测试代码,模拟输出C盘的结构
public class Client {
public static void main(String[] args){
Directory root = new Folder("C");
root.addDir(new Folder("windows"));
Directory program = new Folder("Program File(x86)");
program.addDir(new Folder("Intellij"));
program.addDir(new File("cache"));
root.addDir(program);
root.addDir(new Folder("windows"));
root.addDir(new File("log.txt"));
root.addDir(new File("null.txt"));
root.print();
}
}
输出结果:
C[windows[], Program File(x86)[Intellij[], cache], windows[], log.txt, null.txt]
三、在Android中的实现
组合模式在Android
的View
和ViewGroup
的嵌套组合使用中得到了很好地展现。采用的事安全的组合模式
省略了View
和ViewGroup
的很多方法,在Android
中,只有ViewGroup
才能放View
(ViewGroup
也是View
),View
并不是容器所以ViewGroup
相对于View
多了几个操作视图的方法。
看看ViewGroup
的声明,大概就明白和View有什么不同了,ViewGroup
继承于View
同时还是实现了ViewParent
和ViewManager
两个接口
public abstract class ViewGroup extends View implements ViewParent, ViewManager
先看看ViewManager
接口,为ViewGroup
提供了管理addView
、updateViewLayout
和removeView
方法操作子View
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
而ViewParent
则是定义了requestLayout
和一些焦点事件的处理方法,高版本的API中还添加了嵌套滑动的一些方法
ViewGroup
除了所实现的这两个接口与View不同以外,还有重要的一点,ViewGroup
是抽象类,将View
的onLayout
重置为抽象方法,也就是ViewGroup
的子类必须实现onLayout
方法来布局子View
。View
的onLayout
方法是空实现,因为对于一个普通的View
来说该方法并没有什么实现价值。
除此之外,在View中比较重的两个方法onMeasure
和onDraw
在ViewGroup
中都没有被重写,相对于onMeasure
方法,在ViewGroup
中增加了一些计算子View
的方法,如measureChildren
,measureChildrenWithManager
等;而对于onDraw
方法,VIewGroup
定义了一个dispatchDraw
方法来调其每一个子View
的onDraw
方法。这样ViewGroup
就是真的像一个容器一样,器职责只是负责对子元素的操作而非具体行为。
四、总结
组合模式与解释器模式有一定类同,都涉及递归的调用,但是组合模式所提供的属性层次结构使得可以同等对待单个对象和对象集合。不过是以牺牲单一原则换来的,而组合模式是通过继承来实现的,这样有缺少些的了扩展性。
优点:
- 清楚的定义层次,同时可以忽略层次差异,方便对层次结构进行控制
- 高层模块可以一致的使用一个组合结构或者其中单个对象,不必关心处理的单个对象还是整个组合结构,简化代码
- 对于枝干构件和叶子构件的新增很方便
- 通过枝干对象和叶子对象的递归组合,可以形成复杂的树形结构,同时保持简单的方式进行控制
缺点:
- 对于增加新构件是很对容器中的构件类型进行限制。不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。