前言
主要内容有:
- 该模式的介绍,包括:
- 引子、意图(大白话解释)
- 类图、时序图(理论规范)
- 该模式的代码示例:熟悉该模式的代码长什么样子
- 该模式的优缺点:模式不是万金油,不可以滥用模式
- 该模式的应用案例:了解它在哪些重要的源码中被使用
结构型——组合模式 Composite
引子
组合模式是为了表示那些层次结构,同时部分和整体也可能是一样的结构,常见的如文件夹或者树。
从上图可以看出,文件系统是一个树结构,树上长有节点。树的节点有两种,一种是树枝节点,即目录,有内部树结构,在图中涂有颜色;另一种是文件,即树叶节点,没有内部树结构。
定义
组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。
在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。
合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全式和透明式,将在类图一节中详细介绍两种形式。
类图
安全式合成模式
安全模式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件类中。
- Component 抽象构件:组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
- Composite 树枝构件:是组合中的分支节点对象,它有子节点。树枝构件类给出所有的管理子对象的方法,如add()、remove()以及getChild()。
- Leaf 树叶构件:叶子对象,叶子结点没有子结点。
透明式合成模式
与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定接口。
代码实现
安全式合成模式
- 抽象构件角色类 Component
public interface Component { /** * 输出组建自身的名称 */ public void printStruct(String preStr); } 复制代码
- 树枝构件角色类 Composite
public class Composite implements Component { /** * 用来存储组合对象中包含的子组件对象 */ private List<Component> childComponents = new ArrayList<Component>(); /** * 组合对象的名字 */ private String name; /** * 构造方法,传入组合对象的名字 * @param name 组合对象的名字 */ public Composite(String name){ this.name = name; } /** * 聚集管理方法,增加一个子构件对象 * @param child 子构件对象 */ public void addChild(Component child){ childComponents.add(child); } /** * 聚集管理方法,删除一个子构件对象 * @param index 子构件对象的下标 */ public void removeChild(int index){ childComponents.remove(index); } /** * 聚集管理方法,返回所有子构件对象 */ public List<Component> getChild(){ return childComponents; } /** * 输出对象的自身结构 * @param preStr 前缀,主要是按照层级拼接空格,实现向后缩进 */ @Override public void printStruct(String preStr) { // 先把自己输出 System.out.println(preStr + "+" + this.name); //如果还包含有子组件,那么就输出这些子组件对象 if(this.childComponents != null){ //添加两个空格,表示向后缩进两个空格 preStr += " "; //输出当前对象的子对象 for(Component c : childComponents){ //递归输出每个子对象 c.printStruct(preStr); } } } } 复制代码
- 树叶构件角色类 Leaf
public class Leaf implements Component { /** * 叶子对象的名字 */ private String name; /** * 构造方法,传入叶子对象的名称 * @param name 叶子对象的名字 */ public Leaf(String name){ this.name = name; } /** * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字 * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进 */ @Override public void printStruct(String preStr) { // TODO Auto-generated method stub System.out.println(preStr + "-" + name); } } 复制代码
客户端调用:
public class Client { public static void main(String[]args){ Composite root = new Composite("服装"); Composite c1 = new Composite("男装"); Composite c2 = new Composite("女装"); Leaf leaf1 = new Leaf("衬衫"); Leaf leaf2 = new Leaf("夹克"); Leaf leaf3 = new Leaf("裙子"); Leaf leaf4 = new Leaf("套装"); root.addChild(c1); root.addChild(c2); c1.addChild(leaf1); c1.addChild(leaf2); c2.addChild(leaf3); c2.addChild(leaf4); root.printStruct(""); } } 复制代码
透明式合成模式
相比上面的安全式只有两处改动:
- Composite :implements Conponent改为extends Conponent,其他地方无变化。
public class Composite extends Component { ... } 复制代码
- Leaf:此类将implements Conponent改为extends Conponent,其他地方无变化。
public class Leaf extends Component { ... } 复制代码
客户端调用:
public class Client { public static void main(String[]args){ Component root = new Composite("服装"); Component c1 = new Composite("男装"); Component c2 = new Composite("女装"); Component leaf1 = new Leaf("衬衫"); Component leaf2 = new Leaf("夹克"); Component leaf3 = new Leaf("裙子"); Component leaf4 = new Leaf("套装"); root.addChild(c1); root.addChild(c2); c1.addChild(leaf1); c1.addChild(leaf2); c2.addChild(leaf3); c2.addChild(leaf4); root.printStruct(""); } } 复制代码
可以看出,客户端无需再区分操作的是树枝对象(Composite)还是树叶对象(Leaf)了;对于客户端而言,操作的都是Component对象。
使用场景
Java集合中的组合模式
HashMap 提供 putAll 的方法,可以将另一个 Map 对象放入自己的存储空间中,如果有相同的 key 值则会覆盖之前的 key 值所对应的 value 值
public class Test { public static void main(String[] args) { Map<String, Integer> map1 = new HashMap<String, Integer>(); map1.put("aa", 1); map1.put("bb", 2); map1.put("cc", 3); System.out.println("map1: " + map1); Map<String, Integer> map2 = new LinkedMap(); map2.put("cc", 4); map2.put("dd", 5); System.out.println("map2: " + map2); map1.putAll(map2); System.out.println("map1.putAll(map2): " + map1); } } 复制代码
优缺点
优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”
缺点
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系
- 不容易限制容器中的构件
- 不容易用继承的方法来增加构件的新功能
两种合成模式:安全性合成模式和透明性合成模式的优劣
- 安全性合成模式是指:从客户端使用合成模式上看是否更安全,如果是安全的,那么就不会有发生误操作的可能,能访问的方法都是被支持的。
- 透明性合成模式是指:从客户端使用合成模式上,是否需要区分到底是“树枝对象”还是“树叶对象”。如果是透明的,那就不用区分,对于客户而言,都是Compoent对象,具体的类型对于客户端而言是透明的,是无须关心的。
对于合成模式而言,在安全性和透明性上,会更看重透明性,毕竟合成模式的目的是:让客户端不再区分操作的是树枝对象还是树叶对象,而是以一个统一的方式来操作。
而且对于安全性的实现,需要区分是树枝对象还是树叶对象。有时候,需要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的。
因此在使用合成模式的时候,建议多采用透明性的实现方式。