在阎宏博士的《JAVA与模式》一书中开头是这样描述合成(Composite)模式的:html
合成模式属于对象的结构模式,有时又叫作“部分——总体”模式。合成模式将对象组织到树结构中,能够用来描述总体与部分的关系。合成模式可使客户端将单纯元素与复合元素同等看待。java
合成模式
合成模式把部分和总体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由它们复合而成的合成对象同等看待。安全
好比,一个文件系统就是一个典型的合成模式系统。下图是常见的计算机XP文件系统的一部分。ide
从上图能够看出,文件系统是一个树结构,树上长有节点。树的节点有两种,一种是树枝节点,即目录,有内部树结构,在图中涂有颜色;另外一种是文件,即树叶节点,没有内部树结构。post
显然,能够把目录和文件当作同一种对象同等对待和处理,这也就是合成模式的应用。this
合成模式能够不提供父对象的管理方法,可是合成模式必须在合适的地方提供子对象的管理方法,诸如:add()、remove()、以及getChild()等。url
合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全式和透明式。spa
安全式合成模式的结构
安全模式的合成模式要求管理汇集的方法只出如今树枝构件类中,而不出如今树叶构件类中。
.net
这种形式涉及到三个角色:code
● 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,能够用来管理全部的子对象。合成对象一般把它所包含的子对象当作类型为Component的对象。在安全式的合成模式里,构件角色并不定义出管理子对象的方法,这必定义由树枝构件对象给出。
● 树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
● 树枝构件(Composite)角色:表明参加组合的有下级子对象的对象。树枝构件类给出全部的管理子对象的方法,如add()、remove()以及getChild()。
源代码
抽象构件角色类
public interfaceComponent {
/*** 输出组建自身的名称 */ public voidprintStruct(String preStr); }
树枝构件角色类
public class Composite implementsComponent {
/*** 用来存储组合对象中包含的子组件对象 */ private List childComponents = new ArrayList(); /*** 组合对象的名字 */ privateString name; /*** 构造方法,传入组合对象的名字 * @paramname 组合对象的名字 */ publicComposite(String name){ this.name =name; } /*** 汇集管理方法,增长一个子构件对象 * @paramchild 子构件对象 */ public voidaddChild(Component child){ childComponents.add(child); } /*** 汇集管理方法,删除一个子构件对象 * @paramindex 子构件对象的下标 */ public void removeChild(intindex){ childComponents.remove(index); } /*** 汇集管理方法,返回全部子构件对象 */ public ListgetChild(){ returnchildComponents; } /*** 输出对象的自身结构 * @parampreStr 前缀,主要是按照层级拼接空格,实现向后缩进 */@Override public voidprintStruct(String preStr) { //先把本身输出 System.out.println(preStr + "+" + this.name); //若是还包含有子组件,那么就输出这些子组件对象 if(this.childComponents != null){ //添加两个空格,表示向后缩进两个空格 preStr += " "; //输出当前对象的子对象 for(Component c : childComponents){ //递归输出每一个子对象 c.printStruct(preStr); } } } }
树叶构件角色类
public class Leaf implementsComponent {
/*** 叶子对象的名字 */ privateString name; /*** 构造方法,传入叶子对象的名称 * @paramname 叶子对象的名字 */ publicLeaf(String name){ this.name =name; } /*** 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字 * @parampreStr 前缀,主要是按照层级拼接的空格,实现向后缩进 */@Override public voidprintStruct(String preStr) { //TODO Auto-generated method stub System.out.println(preStr + "-" +name); } }
客户端类
public classClient {
public static voidmain(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)给出了addChild()、removeChild()以及getChild()等方法的声明和实现,而树叶构件类则没有给出这些方法的声明或实现。这样的作法是安全的作法,因为这个特色,客户端应用程序不可能错误地调用树叶构件的汇集方法,由于树叶构件没有这些方法,调用会致使编译错误。
安全式合成模式的缺点是不够透明,由于树叶类和树枝类将具备不一样的接口。
透明式合成模式的结构
与安全式的合成模式不一样的是,透明式的合成模式要求全部的具体构件类,不论树枝构件仍是树叶构件,均符合一个固定接口。
源代码
抽象构件角色类
public abstract classComponent {
/*** 输出组建自身的名称 */ public abstract voidprintStruct(String preStr); /*** 汇集管理方法,增长一个子构件对象 * @paramchild 子构件对象 */ public voidaddChild(Component child){ /*** 缺省实现,抛出异常,由于叶子对象没有此功能 * 或者子组件没有实现这个功能 */ throw new UnsupportedOperationException("对象不支持此功能"); } /*** 汇集管理方法,删除一个子构件对象 * @paramindex 子构件对象的下标 */ public void removeChild(intindex){ /*** 缺省实现,抛出异常,由于叶子对象没有此功能 * 或者子组件没有实现这个功能 */ throw new UnsupportedOperationException("对象不支持此功能"); } /*** 汇集管理方法,返回全部子构件对象 */ public ListgetChild(){ /*** 缺省实现,抛出异常,由于叶子对象没有此功能 * 或者子组件没有实现这个功能 */ throw new UnsupportedOperationException("对象不支持此功能"); } }
树枝构件角色类,此类将implements Conponent改成extends Conponent,其余地方无变化。
public class Composite extendsComponent {
/*** 用来存储组合对象中包含的子组件对象 */ private List childComponents = new ArrayList(); /*** 组合对象的名字 */ privateString name; /*** 构造方法,传入组合对象的名字 * @paramname 组合对象的名字 */ publicComposite(String name){ this.name =name; } /*** 汇集管理方法,增长一个子构件对象 * @paramchild 子构件对象 */ public voidaddChild(Component child){ childComponents.add(child); } /*** 汇集管理方法,删除一个子构件对象 * @paramindex 子构件对象的下标 */ public void removeChild(intindex){ childComponents.remove(index); } /*** 汇集管理方法,返回全部子构件对象 */ public ListgetChild(){ returnchildComponents; } /*** 输出对象的自身结构 * @parampreStr 前缀,主要是按照层级拼接空格,实现向后缩进 */@Override public voidprintStruct(String preStr) { //先把本身输出 System.out.println(preStr + "+" + this.name); //若是还包含有子组件,那么就输出这些子组件对象 if(this.childComponents != null){ //添加两个空格,表示向后缩进两个空格 preStr += " "; //输出当前对象的子对象 for(Component c : childComponents){ //递归输出每一个子对象 c.printStruct(preStr); } } } }
树叶构件角色类,此类将implements Conponent改成extends Conponent,其余地方无变化。
public class Leaf extendsComponent {
/*** 叶子对象的名字 */ privateString name; /*** 构造方法,传入叶子对象的名称 * @paramname 叶子对象的名字 */ publicLeaf(String name){ this.name =name; } /*** 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字 * @parampreStr 前缀,主要是按照层级拼接的空格,实现向后缩进 */@Override public voidprintStruct(String preStr) { //TODO Auto-generated method stub System.out.println(preStr + "-" +name); } }
客户端类的主要变化是再也不区分Composite对象和Leaf对象。
public classClient {
public static voidmain(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对象。
两种实现方法的选择
这里所说的安全性合成模式是指:从客户端使用合成模式上看是否更安全,若是是安全的,那么就不会有发生误操做的可能,能访问的方法都是被支持的。
这里所说的透明性合成模式是指:从客户端使用合成模式上,是否须要区分究竟是“树枝对象”仍是“树叶对象”。若是是透明的,那就不用区分,对于客户而言,都是Compoent对象,具体的类型对于客户端而言是透明的,是无须关心的。
对于合成模式而言,在安全性和透明性上,会更看重透明性,毕竟合成模式的目的是:让客户端再也不区分操做的是树枝对象仍是树叶对象,而是以一个统一的方式来操做。
并且对于安全性的实现,须要区分是树枝对象仍是树叶对象。有时候,须要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的。
所以在使用合成模式的时候,建议多采用透明性的实现方式。