合成(Composite)模型模式属于对象的结构模式,有时又叫做部分-整体(Part-Whole)模式。合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式可以使客户端将单纯元素与复合元素同等看待。
在面向对象的语言中,树结构也同样重要。一个基于继承的类型的等级结构便是一个树结构;下面是一个关于HashMap类关于基于继承的树形结构。如下图:
上图中,HashMap继承自AbstractMap,而AbstractMap继承自Object。同时,HashMap的子类有FdMap、LinkedHashMap、PrinterStateReasons、ProcessEnvironment、PropertyMap等等,其中LinkedHashMap、HashMap、AbstractMap和Object类都是树枝对象,而其他的是树叶对象。
一个基于合成的对象结构也是一个树结构。假如存在一个对象Tree,该对象包含A和B两个对象,A对象包含C对象,而C对象包含D对象。代码如下:
public class Tree { private A a; private B b; } public class A { private C c; } public class B {} public class C { private D d; } public class D {}
上面的Tree对象结构树如下图:
上图中,可以清晰的看见对象结构也是一棵树。
总结
合成模式把部分和整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由成分对象复合而成的合成对象同等看待。如上面的Tree和A、B、C、D对象。
由于合成模式对象结构树中存在树枝和树叶。树枝存在管理子对象(树枝/树叶)的方法,而树叶对象中不存在这些方法。使用Java实现时,根据将这些管理方法放置的位置不同,将合成模式分为透明方式和安全方式。分别如下:
安全方式:指对客户而言看见树枝和树叶对象提供的方法不一样,树枝对象存在管理子对象的方法,而树叶对象不存在管理子对象的方法。
透明方式:指对客户而言看见树枝和树叶对象提供的方法都是一样的,但是树叶对象将管理子对象的方法采用空实现或者抛出UnsupportedOperationException异常,即该对象不支持该方法。
安全式合成模式要求管理子对象的方法只出现在树枝对象中,而不出现在树叶对象中。类图如下图:
上面类图中,Component角色中不存在管理子对象的方法定义。这是由于安全方式的合成模式造成的。这种形式设计到三个角色:
抽象构件(Component)角色:该角色给参加组合的对象定义出公共的接口及其默认行为,但是该角色中不存在管理子对象的方法。如果存在,则树枝和树叶都将存在这些方法,因为两者都实现了了该角色。
树叶构件(Left)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝构件类给出所有的管理子对象的方法,如:add()、remove()等等。
Java代码如下:
// 抽象构建角色 public interface Component { // 返回自己的实例 public Composite getComposite(); // 简单操作 public void sampleOperation(); } // 树枝构建角色 public class Composite implements Component { private Vector<Component> componentVector = new Vector<Component>(); public Composite getComposite() { return this; } // 简单操作 public void sampleOperation() { Enumeration<Component> enumeration = components(); while (enumeration.hasMoreElements()) { Component component = enumeration.nextElement(); component.sampleOperation(); } } // 添加组件 public void add(Component component) { componentVector.addElement(component); } // 移除组件 public void remove(Component component) { componentVector.removeElement(component); } // 获取组件迭代器 public Enumeration<Component> components() { return componentVector.elements(); } } // 树叶构建角色 public class Leaf implements Component { public Composite getComposite() { System.out.println(this.getClass().getName() + " :: getComposite()"); return null; } public void sampleOperation() { System.out.println(this.getClass().getName() + " :: sampleOperation()"); } }
与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝还是树叶构建,均符合一个固定的接口。类图如下:
这种形式涉及到三个角色:
抽象构建(Component)角色:这是一个抽象角色,它给参加组合的对象规定一个接口,规范共有的接口及默认行为。这个接口可以用来管理所有的子对象,提供了管理子对象的方法add()、remove()以及getChild()之类的方法。
树叶构件(Leaf)角色:代表参加组合的树叶对象,定义出参加组合的原始对象行为。树叶对象给出了管理子对象的方法的空实现。
树枝构件(Composite)角色:代表参加组合的有子对象的对象。给出了管理子对象的方法具体实现。
Java代码如下:
// 抽象构建 public interface Component { public void sampleOperation(); public Composite getComposite(); /**************************************************** * 声明管理子对象的方法 ***************************************************/ public void add(Component component); public void remove(Component component); public Enumeration<Component> components(); } // 树枝对象角色 public class Composite implements Component { private Vector<Component> componentVector = new Vector<Component>(); public Composite getComposite() { return this; } public void sampleOperation() { Enumeration<Component> enumeration = components(); while (enumeration.hasMoreElements()) { ((Component) enumeration.nextElement()).sampleOperation(); } } public void add(Component component) { componentVector.addElement(component); } public void remove(Component component) { componentVector.removeElement(component); } public Enumeration<Component> components() { return componentVector.elements(); } } // 树叶对象 public class Leaf implements Component { private Vector<Component> componentVector = new Vector<Component>(); public void sampleOperation() { // Write your code here } public Composite getComposite() { // Write your code here return null; } /**************************************************** * 管理子对象的方法 ***************************************************/ public void add(Component component) { componentVector.addElement(component); } public void remove(Component component) { componentVector.removeElement(component); } public Enumeration<Component> components() { // Write your code here return null; } }
我们通过一个绘图的例子来描述合成模式的应用。假如我们有一个绘图系统给出各种工具用来绘制线、长方形和圆形等基本图形组成图片。类图如下:
Java代码如下:
public abstract class Graphics { public abstract void draw(); } public class Line extends Graphics { public void draw() { System.out.println("绘制一条线(Line)"); } } public class Circle extends Graphics { public void draw() { System.out.println("绘制一个圆(Circle)"); } } public class Rectangle extends Graphics { public void draw() { System.out.println("绘制一个矩形(Rectangle)"); } } /** * 一张图片 * 该图片由Line(直线)、Circle(圆)、Rectangle(矩形)组成 */ public class Picture extends Graphics { // 管理组件 private Vector<Graphics> list = new Vector<Graphics>(10); /** * 绘制一张图片,通过调用每个图片组件的 draw() 方法 */ public void draw() { System.out.println("-> 开始绘制图片"); for (int i = 0; i < list.size(); i++) { Graphics g = (Graphics) list.get(i); g.draw(); } System.out.println("-> 绘制图片成功"); } /** * 添加一个组件 * * @param g */ public void add(Graphics g) { list.add(g); } /** * 移除一个组件 * * @param g */ public void remove(Graphics g) { list.remove(g); } /** * 获取指定下标的组件 * * @param i * @return */ public Graphics getChild(int i) { return (Graphics) list.get(i); } } public class Client { public static void main(String[] args) { // 圆 Circle circle = new Circle(); // 线 Line line = new Line(); // 矩形 Rectangle rectangle = new Rectangle(); Picture picture = new Picture(); // 添加图片的组成部分 picture.add(circle); picture.add(line); picture.add(rectangle); // 绘制图片 picture.draw(); } }
参考资料
《Java与模式》