设计模式之组合模式

1 引用

大型企业都会有总部直属部门和分公司,分公司则又会有各自的小部门;这里,小部门组成了分公司,分公司和直属部门组成总公司,当总公司下发通知的时候,需要经过每一个分公司到达各子部门。这种结构类似于树形结构:
树形组织
要想用代码实现上述发送通知的功能,一般思路会有点复杂 ,需要构建总部直属部门的类、分公司类以及分公司子部门的类,在代码结构中,这样显得非常冗余,而且一旦改变或者新增一个大区,则会增加修改的类。
仔细观察上述树形组织架构图,简化后发现应该是下面这个样子:
简化树形结构
即抽象模型只有公司和部门两个节点,而且公司是父节点,部门是子节点。但是,公司应该还包括子公司,所以这里的部门应该是广义意义上的一个部门,因为子公司也是属于总公司的一个部门;所以公司可以包含部门和子公司,部门只能是它本身,在某种意义上来讲,公司也是一个部门,因此这里的公司和部门应该是一个类的两种不同表现形式,因此可以把其抽象为一个抽象类。
抽象类Unit:

public abstract class Unit {
	private String name;  // 公司或者部门名称
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	
	public abstract void add(Unit unit); // 可以增加部门或者子公司
	public abstract void remove(Unit unit); // 可以移除部门或者子公司
	public abstract void inform(int depth);// 具体业务方法
}

公司类HeadQuarters:

public class HeadQuarters extends Unit {
	
	public HeadQuarters(String name) {
		setName(name);
	}
	// 公司含有下属部门或者子公司
	List<Unit> unitList = new ArrayList<Unit>();

	@Override
	public void add(Unit unit) {
		unitList.add(unit);
	}
	@Override
	public void remove(Unit unit) {
		unitList.remove(unit);
	}
	@Override
	public void inform(int depth) {
		String temp = "";
		for (int i = 0; i < depth; i++) {
			temp += "+";
		}
		System.out.println(temp + getName());
		// 若含有子公司,应该遍历各子公司,直到部门层级
		for (Unit unit : unitList) {
			unit.inform(depth+3);
		}
	}
}

公司类内增加子公司或者部门,因此需要增加一个集合,并在业务方法中对这个集合进行遍历操作,以便迭代集合。

部门类Department:

public class Department extends Unit {
	public Department(String depart) {
		setName(depart);
	}
	@Override
	public void add(Unit unit) {
		System.out.println("部门无法添加单元...");
	}
	@Override
	public void remove(Unit unit) {
		System.out.println("部门无法移除单元...");
	}
	@Override
	public void inform(int depth) {
		String temp = "";
		for (int i = 0; i < depth; i++) {
			temp += "+";
		}
		System.out.println(temp + getName() + "收到通知");
	}
}

因为部门无法含有部门或者子公司,因此响应的操作也就无法进行,且业务方法落实到部门层级。这也就是在公司类内迭代集合的目的。

客户端类:

public class Client {
	public static void main(String[] args) {
		Unit headQuartersUnit = new HeadQuarters("总部");
		
		Unit financialDepart = new Department("财务部");
		Unit employDepart = new Department("人事部");
		
		Unit northChinaUnit = new HeadQuarters("华北大区");
		Unit northChinaFinancial = new Department("华北大区财务部");
		Unit northChinaEmploy = new Department("华北大区人事部");
		
		Unit southernChinaUnit = new HeadQuarters("华南大区");
		Unit southernChinaFinancial = new Department("华南大区财务部");
		Unit southernChinaEmploy = new Department("华南大区人事部");
		
		headQuartersUnit.add(financialDepart);
		headQuartersUnit.add(employDepart);
		headQuartersUnit.add(northChinaUnit);
		headQuartersUnit.add(southernChinaUnit);
		
		northChinaUnit.add(northChinaFinancial);
		northChinaUnit.add(northChinaEmploy);
		
		southernChinaUnit.add(southernChinaEmploy);
		southernChinaUnit.add(southernChinaFinancial);
		
		headQuartersUnit.inform(0);
	}
}

运行结果:

总部
+++财务部收到通知
+++人事部收到通知
+++华北大区
++++++华北大区财务部收到通知
++++++华北大区人事部收到通知
+++华南大区
++++++华南大区人事部收到通知
++++++华南大区财务部收到通知

最后的输出结果跟上述组织结构图一样。

类似于这种代码结构的模式称为组合模式。

2 组合模式原理

《大话设计模式》中这样定义组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。在本示例中,单个对象是指部门,组合对象则是指公司,由于单个对象(部门)和组合对象(公司)继承同一个抽象类,因此在客户端使用时具有一致性,但是单个对象内不允许包含其他类,因此个别方法需要进行异常处理。
组合模式的UML图:
组合模式
UML类图中各角色:
Component(抽象构建):是节点的抽象类,包含所有节点类的行为和实现,如部门类和公司类;针对节点公司类,抽象构建中含有增加、移除和遍历自身的功能;
Leaf(叶子构建):继承或者实现抽象构建,是组合结构中的叶子节点,叶子节点没有子节点,针对抽象构建中对子节点的操作方法可以通过异常方式处理掉;
Composite(容器构建):继承或者实现抽象构建,是组合结构中的容器节点,容器节点包含子节点,子节点可以是叶子节点,也可以是容器节点,它内部有一个存储子节点的容器,并提供一系列的方法操作容器,在业务方法中可以递归调用其他子节点的业务方法。
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。

这里或许我们可以对上述代码做一个小的修改,具体代码就不列出来,UML类图如图所示:
组合透明模式
唯一的变化就是在抽象构建Component类中,对add()/remove()方法提供了默认实现,这样在叶子节点中就省去了很多代码,容器类则继续重写父类的方法;这样做的目的是什么呢?客户端生成叶子节点对象的时候,则可以直接生成Leaf引用的对象,而不需要生成Component引用的对象,避免叶子节点调用时出现的异常;但这样做带来了不透明的使用方式,即在客户端不能全部针对抽象构建编程,需要使用具体叶子构建类型来定义叶子对象。相比不透明组合模式,第一种则是透明组合模式,是标准的组合模式。

第二种修改的思路是把抽象构建类中对容器的操作方法移到具体容器类中,容器类根据实际需要增加相应的操作。具体看UML类图:
组合模式安全模式
这样做就可以避免透明组合模式中叶子节点透不透明的问题,因此叶子节点压根不需要考虑异常情况;但这种做法同样带来问题,即只能声明容器类引用的对象,而不能面向接口编程,造成客户端对容器类的不透明。相对于叶子节点来说,这种做法能保证它的安全性,因此称为安全组合模式。

3 组合模式特点

组合模式的优点有哪些?
(1) 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
(2) 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
(3) 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
(4) 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
组合模式的缺点有哪些?
透明组合模式和安全组合模式在某种意义上讲都是不安全,各有利弊。

4 组合模式使用场景

(1) 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
(2) 在一个使用面向对象语言开发的系统中需要处理一个树形结构。
(3) 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型

5 参考资料

《大话设计模式》
https://www.cnblogs.com/lfxiao/p/6816026.html
https://www.cnblogs.com/lixiuyu/p/5947120.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值