合成(composite)模式属于对象的结构模式,有时又叫部分-整体模式。合成模式将对象组织到数结构中,可以用来描述整体与部分的关系。
文件系统
一个文件系统就是一个典型的合成模式系统。下图所示就是常见的PC文件系统的一部分。
文件系统是一个树结构,树上长有节点。树的节点有两种,一种是树枝节点,即目录,有内部树结构;一种是树叶节点,即文件。
显然,可以把目录和文件当做同一种对象看待和处理,这就是合成模式的应用。
合成模式可以不提供管理父对象的管理方法,但是合成模式必须在合适的地方管理子对象的管理方法。在什么地方声明子对象的管理方法,如add()、remove()、getChild()等方法。根据提供管理方法的位置不同分为两种:透明方式和安全方式。
安全模式的合成模式
此模式要求管理聚集的方法只出现在数枝构件类中,而不出现在树叶构件类中。其类图如下:
涉及到3个角色:
抽象构件(Component)角色: 这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。合成对象通常把它所包含的子对象当做类型为Component的对象。在安全式的合成模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝构件对象给出。
树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝构件类给出所有的管理子对象的方法,如add()、remove()以及getChild()。
代码如下:
抽象构件角色
package cn.qlq.composite; public interface Component { /** * 返回自身实例 * * @return */ Component getComponent(); /** * 打印组件信息 */ void printComponent(); }
叶子节点
package cn.qlq.composite; public class Leaf implements Component { private String name; public Leaf(String name) { super(); this.name = name; } @Override public Component getComponent() { return this; } @Override public void printComponent() { System.out.println(this); } @Override public String toString() { return "Leaf [name=" + name + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
数枝节点:
package cn.qlq.composite; import java.util.ArrayList; import java.util.List; public class Composite implements Component { private String name; private List<Component> components = new ArrayList<>(); public Composite(String name) { super(); this.name = name; } @Override public Component getComponent() { return this; } @Override public void printComponent() { System.out.println(this); } public void add(Component component) { if (!components.contains(component)) { components.add(component); } } public void remove(Component component) { if (components.contains(component)) { components.remove(component); } } public List<Component> getChilds() { return components; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Component> getComponents() { return components; } public void setComponents(List<Component> components) { this.components = components; } @Override public String toString() { return "Composite [name=" + name + ", components=" + components + "]"; } }
测试代码:
package cn.qlq.composite; public class Client { public static void main(String[] args) { Composite component = new Composite("component0"); Composite component001 = new Composite("component001"); Composite component002 = new Composite("component002"); Component Leaf001 = new Leaf("leaf001"); Component Leaf002 = new Leaf("leaf002"); // 维护关系 component001.add(Leaf001); component002.add(Leaf002); component.add(component001); component.add(component002); component.printComponent(); } }
结果:
Composite [name=component0, components=[Composite [name=component001, components=[Leaf [name=leaf001]]], Composite [name=component002, components=[Leaf [name=leaf002]]]]]
优缺点:
优点是是一种安全的做法,因为树叶节点没有add()等方法,因此客户端对树叶节点调用这些方法时编译期间就不会通过。
缺点是不够透明,因为树叶类和合成类将具有不同的接口,而且客户端调用相关方法需要区分树叶节点和数枝节点。
透明模式的合成模式
与安全模式不同的是,透明模式的合成模式要求所有的具体构件类,无论是叶子节点还是树叶节点,均符合一个固定的接口,也就是在所有节点都具备add()、remove()、getChild()等管理方法。只不过在叶子节点的不相干方法中不做任何操作。
其类图如下:
涉及到的角色:
抽象构件(Component)角色: 这是一个抽象角色,它给参加组合的对象规定一个接口,规范共用的接口和默认行为。这个接口可以用来管理所有的子对象,要提供一个接口以规范取得和管理下层组件的接口,包括add、remove、getChild等管理方法。
树叶构件(Leaf)角色:代表参加组合的树叶对象,定义出参加组合的原始对象的行为。树叶类会给出add、remove、getChild等方法的平庸实现(什么都不做,空方法)。
树枝构件(Composite)角色:代表参加组合的有子对象的对象,定义出这样对象的行为。
代码如下:
package cn.qlq.composite; import java.util.List; public interface Component { /** * 返回自身实例 * * @return */ Component getComponent(); /** * 打印组件信息 */ void printComponent(); void add(Component component); void remove(Component component); List<Component> getChilds(); }
package cn.qlq.composite; import java.util.List; public class Leaf implements Component { private String name; public Leaf(String name) { super(); this.name = name; } @Override public Component getComponent() { return this; } @Override public void printComponent() { System.out.println(this); } @Override public String toString() { return "Leaf [name=" + name + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void add(Component component) { // ignored } @Override public void remove(Component component) { // ignored } @Override public List<Component> getChilds() { return null; } }
package cn.qlq.composite; import java.util.ArrayList; import java.util.List; public class Composite implements Component { private String name; private List<Component> components = new ArrayList<>(); public Composite(String name) { super(); this.name = name; } @Override public Component getComponent() { return this; } @Override public void printComponent() { System.out.println(this); } public void add(Component component) { if (!components.contains(component)) { components.add(component); } } public void remove(Component component) { if (components.contains(component)) { components.remove(component); } } public List<Component> getChilds() { return components; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Component> getComponents() { return components; } public void setComponents(List<Component> components) { this.components = components; } @Override public String toString() { return "Composite [name=" + name + ", components=" + components + "]"; } }
客户端代码:主要变化是不再区分树叶和数枝,静态类型直接为接口,实际类型为数枝和树叶。
package cn.qlq.composite; public class Client { public static void main(String[] args) { Component component = new Composite("component0"); Component component001 = new Composite("component001"); Component component002 = new Composite("component002"); Component Leaf001 = new Leaf("leaf001"); Component Leaf002 = new Leaf("leaf002"); // 维护关系 component001.add(Leaf001); component002.add(Leaf002); component.add(component001); component.add(component002); component.printComponent(); } }
结果:
Composite [name=component0, components=[Composite [name=component001, components=[Leaf [name=leaf001]]], Composite [name=component002, components=[Leaf [name=leaf002]]]]]
优缺点:
这么做的好处是所有的构件类都有相同的接口,在客户端看来,树叶类对象与数枝对象的区别起码在接口层次上消失了。
缺点是不安全,因为树叶节点和数枝节点在本质上是有区别的。树叶对象不可能有子节点的存在。因此调用树叶节点的add()、remove()、等方法容易引起运行时错误。
两种模式的选择:
安全性合成模式是指:从客户端使用合成模式上看是否更安全,如果是安全的,那么就不会有发生误操作的可能,能访问的方法都是被支持的。
透明性合成模式是指:从客户端使用合成模式上,是否需要区分到底是“树枝对象”还是“树叶对象”。如果是透明的,那就不用区分,对于客户而言,都是Component对象,具体的类型对于客户端而言是透明的,是无须关心的。
对于合成模式而言,在安全性和透明性上,会更看重透明性,毕竟合成模式的目的是:让客户端不再区分操作的是树枝对象还是树叶对象,而是以一个统一的方式来操作。
而且对于安全性的实现,需要区分是树枝对象还是树叶对象。有时候,需要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的。
因此在使用合成模式的时候,建议多采用透明性的实现方式。