我们很容易将“组合模式”和“组合关系”搞混。组合模式最初只是用于解决树形结构的场景,更多的是处理对象组织结构之间的问题。而组合关系则是通过将不同对象封装起来完成一个统一功能.
1 组合模式介绍
将对象组合成树形结构以表示整个部分的层次结构.组合模式可以让用户统一对待单个对象和对象的组合.
2 组合模式原理
3 组合模式实现
组合模式的关键在于定义一个抽象根节点类,它既可以代表叶子,又可以代表树枝节点,客户端就是针对该抽象类进行编程,不需要知道它到底表示的是叶子还是容器,可以对其进行统一处理.
树枝节点对象和抽象根节点类之间建立了一个聚合关联关系,在树枝节点对象中既可以包含叶子节点,还可以继续包含树枝节点,以此实现递归组合,形成一个树形结构.
/**
* 抽象根节点角色
* 对客户端而言,只需要针对抽象编程,无需关心具体子类是树枝节点还是叶子节点
**/
public abstract class Component {
public abstract void add(Component c); //增加节点
public abstract void remove(Component c); //删除节点
public abstract Component getChild(int i); //获取节点
public abstract void operation(); //业务方法
}
/**
* 叶子节点
* 叶子节点中不能包含子节点
**/
public class Leaf extends Component {
@Override
public void add(Component c) {
}
@Override
public void remove(Component c) {
}
@Override
public Component getChild(int i) {
return null;
}
@Override
public void operation() {
//叶子节点中的具体方法
}
}
/**
* 树枝节点
* 树枝节点类是一个容器对象,它既可以包含树枝节点也可以包含叶子节点
**/
public class Composite extends Component {
//定义集合属性,保存子节点的数据
private ArrayList<Component> list = new ArrayList<>();
@Override
public void add(Component c) {
list.add(c);
}
@Override
public void remove(Component c) {
list.remove(c);
}
@Override
public Component getChild(int i) {
return list.get(i);
}
//具体业务方法
@Override
public void operation() {
//在循环中,递归调用其他节点中的operation() 方法
for (Component component : list) {
component.operation();
}
}
}
4 组合模式应用实例
我们按照下图的表示,进行文件和文件夹的构建.
Entry类: 抽象类,用来定义File类和Directory类的共性内容
/**
* Entry抽象类 (文件夹+文件)
**/
public abstract class Entry {
public abstract String getName(); //获取文件名
public abstract int getSize(); //获取文件大小
//添加文件或者文件夹方法
public abstract Entry add(Entry entry);
//显示指定目录下的所有文件的信息
public abstract void printList(String prefix);
@Override
public String toString() {
return getName() +"(" + getSize() +")";
}
}
File类,叶子节点,表示文件.
/**
* File类,表示文件
**/
public class File extends Entry{
private String name; //文件名
private int size; //文件大小
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return this.name;
}
@Override
public int getSize() {
return this.size;
}
@Override
public Entry add(Entry entry) {
return null;
}
@Override
public void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
Directory类,树枝节点,表示文件
/**
* Directory 容器对象,表示文件夹
**/
public class Directory extends Entry {
//文件的名字
private String name;
//文件夹和文件的集合
private ArrayList<Entry> directory = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
/**
* 获取文件大小
* 1.如果entry对象是file类型,则调用getSize方法获取文件大小
* 2.如果entry对象是Directory类型,会继续调用子文件夹的getSize()方法,形成递归调用
*/
@Override
public int getSize() {
int size = 0;
//遍历获取文件大小
for (Entry entry : directory) {
size += entry.getSize();
}
return size;
}
@Override
public Entry add(Entry entry) {
directory.add(entry);
return this;
}
@Override
public void printList(String prefix) {
System.out.println("/" + this);
for (Entry entry : directory) {
entry.printList("/" + name);
}
}
}
测试
public class Client {
public static void main(String[] args) {
//创建根节点
Directory rootDir = new Directory("root");
//创建树枝节点
Directory binDir = new Directory("bin");
//向bin目录添加叶子节点
binDir.add(new File("vi",10000));
binDir.add(new File("test",20000));
Directory tmpDir = new Directory("tmp");
Directory usrDir = new Directory("usr");
Directory mysqlDir = new Directory("mysql");
mysqlDir.add(new File("my.cnf",30));
mysqlDir.add(new File("test.db",25000));
usrDir.add(mysqlDir);
//将所有子文件夹封装到根节点
rootDir.add(binDir);
rootDir.add(tmpDir);
rootDir.add(usrDir);
rootDir.printList("");
}
}
5 组合模式总结