组合模式(Composite Pattern)也称为部分整体模式(Part-Whole Pattern),结构型设计模式之一,组合模式比较简单,它将一组相似的对象看作一个对象处理,江根据一个树关结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别,生活中比较典型的例子-组织结构树状图
在这么一个结构中可以看到虽然总公司和子公司其本质不一样,但是它在我们的组织结构中是一样的,,我们可以把它们看作一个抽象的公司,在组合模式中我们将这样的一个拥有分支的节点称之为枝干构件,位于树状结构顶部的枝干结构比较特殊,我们称为根结构件,因为其为整个树状图的始端,同样对于像行政部和研发部这样没有分支的结构,我们称为叶子构件,这样的一个结构就是组合模式的雏形。
定义:
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
使用场景:
1.表示对象的部分-整体层次结构时。
2.从一个不休中能够独立出部分模块或功能的场景。
UML类图:
Component:抽象根节点,为组合中的对象声明接口。在适当的情况下,实现所有类共有接口的缺省行为。声明一个接口用于访问和管理Component的子节点。可在递归结构中定义一个接口,用于访问一个父节点,并在合适的情况下实现它。
Composite:定义有子节点的那些枝干节点的行为,存储子节点,在Component接口中实现与子节点有关的操作。
Leaf:在组合中表示叶子节点对象,叶子节点没有子节点,在组合中定义节点对象的行为。
Client:通过Component接口操纵组合节点的对象。
示例:
通用模式代码:
/**
* 抽象根节点
* @author Administrator
*
*/
public abstract class Component {
protected String name; //节点名
public Component(String name){
this.name = name;
}
/**
* 具体的逻辑方法由子类实现
*/
public abstract void doSomething();
}
/**
* 具体枝干节点
* @author Administrator
*
*/
public class Composite extends Component{
/**
* 存储节点的容器
*/
private List<Component> components = new ArrayList<Component>();
public Composite(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println("name : " + name);
if(components != null){
for(Component c : components){
c.doSomething();
}
}
}
/**
* 添加子节点
* @param child
*/
public void addChild(Component child){
components.add(child);
}
/**
* 移除子节点
* @param child
*/
public void removeChild(Component child){
components.remove(child);
}
public Component getChildren(int index){
return components.get(index);
}
}
/**
* 具体叶子节点类
* @author Administrator
*
*/
public class Leaf extends Component{
public Leaf(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println("leaf name : " + name);
}
}
public class Client {
public static void main(String[] args) {
//构建根节点
Composite root = new Composite("Root");
//构建两个枝干节点
Composite branch1 = new Composite("branch1");
Composite branch2 = new Composite("branch2");
//构建两个叶子节点
Leaf leaf1 = new Leaf("leaf1");
Leaf leaf2 = new Leaf("leaf2");
//将叶子节点添加至枝干节点中
branch1.addChild(leaf1);
branch2.addChild(leaf2);
//将枝干节点添加至根节点中
root.addChild(branch1);
root.addChild(branch2);
root.doSomething();
}
}
这与面向对象的依赖倒置原则相违背,我们所定义的抽象Component在这里起的作用就不大,既然是面向接口编程,那么我们就该把更多的把焦点放在接口的设计上,如果我们稍微修改上面的类图将位于Composite中的一些实现方法定义到Component中,那么我们会得到一个不一样的组合模式。
将组合所使用的方法定义在抽象类的方式称为透明的组合模式,而上面我们所说的组合模式则称为安全的组合模式,透明组合模式中不管是叶子节点还是枝干节点都有着相同的结构,那意味着我们无法通过getChildren方法得到子节点的类型,崦必须在方法实现的内部进行判断,
/**
* 透明的组合模式抽象根节点
* @author Administrator
*
*/
public abstract class Component {
protected String name; //节点名
public Component(String name){
this.name = name;
}
public abstract void doSomething();
/**
* 添加子节点
* @param child
*/
public abstract void addChild(Component child);
/**
* 移除子节点
* @param child
*/
public abstract void removeChild(Component child);
/**
* 获取子节点
* @param index
* @return
*/
public abstract Component getChildren(int index);
}
/**
* 透明的组合模式叶子节点
* @author Administrator
*
*/
public class Leaf extends Component{
public Leaf(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println("leaf name : " + name);
}
@Override
public void addChild(Component child) {
throw new UnsupportedOperationException("叶子节点没有子节点.");
}
@Override
public void removeChild(Component child) {
throw new UnsupportedOperationException("叶子节点没有子节点.");
}
@Override
public Component getChildren(int index) {
throw new UnsupportedOperationException("叶子节点没有子节点.");
}
}
public class Client {
public static void main(String[] args) {
//构建根节点
com.peak.toumingzuhemoshi.Composite root = new Composite("Root");
//构建两个枝干节点
com.peak.toumingzuhemoshi.Composite branch1 = new Composite("branch1");
com.peak.toumingzuhemoshi.Composite branch2 = new Composite("branch2");
//构建两个叶子节点
com.peak.toumingzuhemoshi.Leaf leaf1 = new Leaf("leaf1");
com.peak.toumingzuhemoshi.Leaf leaf2 = new Leaf("leaf2");
//将叶子节点添加至枝干节点中
branch1.addChild(leaf1);
branch2.addChild(leaf2);
//将枝干节点添加至根节点中
root.addChild(branch1);
root.addChild(branch2);
root.doSomething();
}
}
组合模式简单实现:
/**
* 表示文件和文件夹的抽象类
* @author Administrator
*
*/
public abstract class Dir {
/**
* 声明一个List成员变量存储文件夹下的所有元素
*/
protected List<Dir> dirs = new ArrayList<Dir>();
private String name; //当前文件或文件夹名
public Dir(String name){
this.name = name;
}
/**
* 添加一个文件或文件夹
* @param dir
*/
public abstract void addDir(Dir dir);
/**
* 删除一个文件或文件夹
* @param dir
*/
public abstract void removeDir(Dir dir);
/**
* 清空文件夹下所有元素
*/
public abstract void clear();
/**
* 输出文件夹目录结构
*/
public abstract void print();
/**
* 获取文件夹下所有的文件或子文件夹
* @return
*/
public abstract List<Dir> getFiles();
/**
* 获取文件或文件夹名
* @return
*/
public String getName(){
return name;
}
}
/**
* 表示文件夹的类
* @author Administrator
*
*/
public class Folder extends Dir{
public Folder(String name) {
super(name);
}
@Override
public void addDir(Dir dir) {
dirs.add(dir);
}
@Override
public void removeDir(Dir dir) {
dirs.remove(dir);
}
@Override
public void clear() {
dirs.clear();
}
@Override
public void print() {
System.out.println(getName() + "(");
Iterator<Dir> iter = dirs.iterator();
while(iter.hasNext()){
Dir dir = iter.next();
dir.print();
if(iter.hasNext()){
System.out.println(", ");
}
}
System.out.println(")");
}
@Override
public List<Dir> getFiles() {
return dirs;
}
//主要就是print方法用来输出文件夹的目录结构,该方法逻辑也比较简单,首先输出当前文件夹名,然后迭代遍历子元素,调用
//子元素的print方法输出其目录结构,如果遇到子元素还是个文件夹,那么递归遍历直至遥输出元素均为文件为止。
}
/**
* 表示文件的类
* @author Administrator
*
*/
public class File extends Dir{
public File(String name) {
super(name);
}
@Override
public void addDir(Dir dir) {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public void removeDir(Dir dir) {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public void clear() {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public void print() {
System.out.println(getName());
}
@Override
public List<Dir> getFiles() {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
}
public class Client {
public static void main(String[] args) {
//构建一个目录对象表示C盘根目录
Dir diskC = new Folder("C");
diskC.addDir(new File("log.txt"));
//C盘根目录下还有3个子目录Windows/PerfLogs/Program File
Dir dirWin = new Folder("Windows");
dirWin.addDir(new File("explorer.exe"));
diskC.addDir(dirWin);
Dir dirPer = new Folder("PerfLogs");
dirPer.addDir(new File("null.txt"));
diskC.addDir(dirPer);
Dir dirPro = new Folder("Program File");
dirPro.addDir(new File("ftp.txt"));
diskC.addDir(dirPro);
diskC.print();
}
}
总结:
组合模式更适用于对一些界面UI的架构设计上,但一般用不到,组合模式与解释器模式有一定的类同,两地者在迭代对象时都涉及递归的调用,但是组合模式所提供的属性层次结构使得我们能够一视同仁地对待单个对象和对象集合,不过这是以牺牲类的单一原则换来的,而且组合模式是通过继承来实现的,这样的做法缺少弹性。
优点:
1.组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让高层模块忽略了层次的差异,方便对整个层次结构进行控制。
2.高层模块可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了高层模块的代码。
3.在组合模式中增加新的枝干构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”
4.组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和枝干对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单
缺点:
在新增构件时不好对枝干中的构件类型进行限制,不能依赖类型系统来施加这些约束,因为在大多数情况下,它们都来自于相同的抽象层,此时,必须行时进行类型检查来实现,这个实现过程较为复杂。