一、概述
我们常常可以看到这样一种形式,比如说电脑中的磁盘管理,我的电脑中有C盘、D盘、E盘,在C盘中又有A文件夹、B文件夹等,在A文件夹下又有A1文件夹、A2文件夹、A3文件等等;再比如说在桌子上有背包、电脑、水杯、书架、笔筒等,在背包里有书、本子、IPad等,在书架上有书A、书B等,在笔筒里有中性笔、钢笔、铅笔、彩笔等;再比如北京公司总部有财务部、人力部、上海分公司、深圳分公司等,在上海分公司又有财务部、人力部、南京办事处、杭州办事处等,在南京办事处下面又有财务部、人力部等。我这么说,可能看不出什么,下面用图表示一下这3个例子。
图1 我的电脑结构示意图
图2 桌面结构示意图
图3 公司结构示意图
这样一看他们结构是不是一目了然,其实就是树形结构,有根、有枝干、有叶子。如果这个时候boss抛来一个需求,让所有的枝干具有一定的功能A,所有的叶子具有一定的功能B,或者,更具体点,从图3来说,要求所有公司的财务部具有相同的功能A,人力部具有功能B,这个时候我们要怎么实现呢。
当当当当~ 组合模式应运而生!
二、组合模式
1. 定义
组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。(引自《大话设计模式》)
2. 基本结构图
Component:抽象类或接口,为组合中的对象声明接口。通过add和remove方法提供增加和删除树枝、树叶的功能。
Leaf:叶子,为组合中最末端的节点,没有子节点。
Composite:枝节点,上有父节点,下有子节点。定义了枝节点的行为,用来存储子部件。在Component接口中实现与子部件有关的操作——增加add和删除remove。
3. 基本代码
Component抽象类
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(Component c);
public abstract void remove(Component c);
public abstract void display(int depth);
}
Leaf叶子类,由于叶子没有子节点,无需再增叶子和树枝,所以这里的add和remove方法就没有实现。但却保留了这两个方法,是为了与树枝节点Composite类保持一致的接口,以消除叶节点和枝节点在抽象层次的区别。
public class Leaf extends Component{
public Leaf(String name) {
super(name);
}
@Override
public void add(Component c) {
}
@Override
public void remove(Component c) {
}
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
}
}
Composite枝节点类
public class Composite extends Component{
private List<Component> children = new ArrayList<Component>();
public Composite(String name) {
super(name);
}
@Override
public void add(Component c) {
children.add(c);
}
@Override
public void remove(Component c) {
if(children.contains(c)) {
children.remove(c);
}
}
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
for (Component component : children) {
component.display(depth+2);
}
}
}
测试类
public class CompositeTest {
public static void main(String[] args) {
//创建根节点
Composite root = new Composite("Root");
//添加两个叶节点
root.add(new Leaf("Leaf A"));
root.add(new Leaf("Leaf B"));
//创建枝节点
Composite c1 = new Composite("Composite X");
//给枝节点X添加两个叶节点
c1.add(new Leaf("Leaf XA"));
c1.add(new Leaf("Leaf XB"));
//将枝节点X添加到根节点上
root.add(c1);
//创建枝节点
Composite c2 = new Composite("Composite XY");
//给枝节点XY添加两个叶节点
c2.add(new Leaf("Leaf XYA"));
c2.add(new Leaf("Leaf XYB"));
//将枝节点XY添加到枝节点X上
c1.add(c2);
//给根节点添加叶节点C
root.add(new Leaf("Leaf C"));
//创建叶节点D
Leaf d = new Leaf("Leaf D");
//将叶节点D添加到根节点上
root.add(d);
//将叶节点D从根节点移除
root.remove(d);
//显示树状结构结果
root.display(1);
}
}
结果:
用一张图来表示这个结果的话,如下
进一步说明,这个例子里的display方法只是为了输出结果显示出比较明确的层次结构,实际应用中可以根据需求做相应调整。
三、透明方式与安全方式
1. 透明方式
上面例子里,叶节点Leaf类里面的add和remove方法其实是无意义的方法,没有实现内容,这种方式叫做透明方式。
即,在Component接口中声明所有用来管理子对象的方法,包括add和remove等,这样实现Component接口的所有子类都具备了add和remove方法。
这样做的优点,上面也提到过了,就是让枝节点和叶节点对外界来说没有区别,具备完全一致的行为接口。
相应的缺点就是,叶节点实现了完全没有任何意义的add和remove方法。
2. 安全方式
与透明方式相对应,如果叶节点Leaf类中不用add和remove方法,这种方式就叫做安全方式。
即,在Component接口中不声明add和remove方法,而是在子部件Composite中声明所有用来管理子类对象的方法。
这样做的优点就是上面提到的透明方式的缺点,这里不会在Leaf类中出现无意义的方法。
相应的缺点也很明显,就是在客户端调用时需要做判断进行区分,稍稍麻烦一些。
四、实战应用
这里就用上面的公司结构来举例说明好了
抽象公司类Company,就是上面的Component
public abstract class Company {
protected String name;
public Company(String name) {
this.name = name;
}
public abstract void add(Company company);
public abstract void remove(Company company);
public abstract void display(int depth);
public abstract void duty();
}
具体公司实现类ConcreteCompany,就是上面的Composite
public class ConcreteCompany extends Company{
//声明公司集合
private List<Company> companies = new ArrayList<Company>();
public ConcreteCompany(String name) {
super(name);
}
/**
* 添加子公司
*/
@Override
public void add(Company company) {
companies.add(company);
}
/**
* 删除子公司
*/
@Override
public void remove(Company company) {
if (companies.contains(company)) {
companies.remove(company);
}
}
/**
* 显示
*/
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
for (Company company : companies) {
company.display(depth+2);
}
}
/**
* 公司职责
*/
@Override
public void duty() {
for (Company company : companies) {
company.duty();
}
}
}
人力部HRCompany,就是上面的叶子类Leaf
/**
* 人力资源部
*/
public class HRDepartment extends Company{
public HRDepartment(String name) {
super(name);
}
@Override
public void add(Company company) {
}
@Override
public void remove(Company company) {
}
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
}
@Override
public void duty() {
System.out.println(name+"职责:招聘人才");
}
}
财务部FinanceCompany,就是上面的Leaf。这个例子中有两个Leaf——人力部和财务部,它们都没有子节点,符合叶子特征,但是由于这里人力部和财务部具有不同的公司职责,所以分开两个实现。
/**
* 财务部
*/
public class FinanceDepartment extends Company{
public FinanceDepartment(String name) {
super(name);
}
@Override
public void add(Company company) {
}
@Override
public void remove(Company company) {
}
@Override
public void display(int depth) {
for(int i=0;i<depth;i++) {
System.out.print("-");
}
System.out.println(name);
}
@Override
public void duty() {
System.out.println(name+"职责:公司财务管理");
}
}
测试类
public class CompositeTest {
public static void main(String[] args) {
ConcreteCompany root = new ConcreteCompany("北京总公司");
root.add(new HRDepartment("总公司人力部"));
root.add(new FinanceDepartment("总公司财务部"));
ConcreteCompany c1 = new ConcreteCompany("上海分公司");
c1.add(new HRDepartment("上海分公司人力部"));
c1.add(new FinanceDepartment("上海分公司财务部"));
root.add(c1);
ConcreteCompany c2 = new ConcreteCompany("南京办事处");
c2.add(new HRDepartment("南京办事处人力部"));
c2.add(new FinanceDepartment("南京办事处财务部"));
c1.add(c2);
ConcreteCompany c3 = new ConcreteCompany("杭州办事处");
c3.add(new HRDepartment("杭州办事处人力部"));
c3.add(new FinanceDepartment("杭州办事处财务部"));
c1.add(c3);
System.out.println("各公司名称:");
root.display(1);
System.out.println("\n各部门职责:");
root.duty();
}
}
结果
五、总结
使用场合:
(1)如果各对象体现出“部分-整体”层次结构(或树形结构)时,可以考虑使用组合模式。
(2)当客户希望忽略单个对象与组合对象的区别,可以使用统一接口调用时,可以考虑使用组合模式。
组合模式,简单易用,核心其实就是一个递归和组合,展开来说就是一个很复杂的对象由许多基本对象构成,其中基本对象又可以构成稍复杂一些的组合对象,而组合对象又可以被重组成更复杂的组合对象,如此递归下去就构成了最终的对象。
写在最后,
本文主要是小猫看了《大话设计模式》的一些记录笔记,再加之自己的一些理解整理出此文,方便以后查阅,仅供参考。