设计模式之组合模式

组合模式又叫合成(部分-整体)模式,属于结构型模式。组合模式将对象组织到树结构中,可以用来描述整体与部分的关系,可以使客户端将单纯元素与复合元素同等看待。

树结构在过程性的编程语言中曾经发挥了巨大的作用,在面向对象的语言中,树结构也同样威力巨大。一个基于继承的类型的等级结构便是一个树结构;一个基于组合的对象结构也是一个树结构。

在树形结构中,最顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,如下图所示:

由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。

这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。

组合模式的主要优点有:

  1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

  1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  2. 不容易限制容器中的构件;
  3. 不容易用继承的方法来增加构件的新功能;

组合模式的实现根据所实现接口的区别分为透明式组合模式安全式组合模式

透明式

作为第一种选择,在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及 getChild()方法。这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等地对待所有的对象。这就是透明形式的合成模式。

这个选择的缺点是不够安全,因为树叶类对象和合成类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及 getChild()方法没有意义,但是在编译时期不会出错,而只会在运行时期才会出错。

透明式的组合模式要求所有的具体构件类,不论树枝构件还是树叶构件,都符合一个固定的接口,类图如下:

透明式组合模式涉及到抽象构件角色、树叶构件角色、树枝构件角色三种模式:

  • 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象规定一个接口,规范共有的接口及默认行为。这个接口可以用来管理所有的子对象,要提供一个接口以规范取得和管理下层组件的接口,包括 add()、remove()以及getChild()之类的方法。
  • 树叶构件(Leaf〉角色:代表参加组合的树叶对象,定义出参加组合的原始对象的行为。树叶类会给出add()、remove()以及getChild()之类的用来管理子类对象的方法的平庸实现。
  • 树枝构件(Composite)角色:代表参加组合的有子对象的对象,定义出这样的对象的行为。

我们都见过画图软件,一个绘图系统给出各种工具用来描绘线、长方形和原形等基本图形组成的图形。一个复杂的图形肯定是有这些基本的图形组成的。本模式我们就以这为例子来讲解。

由于一个复杂的图形是由基本图形组合而成的,因此,一个组合的图形应当有一个列表,存储对所有的基本图形对象的引用。复合图形的draw()方法在调用时,应当逐一调用所有列表上的基本图形对象的draw()方法。

透明形式的组合模式意味着不仅只有树枝构件角色才配备有管理聚集的方法,树叶构件也有这些方法,虽然树叶构件的这些方法是平庸的。透明式的组合模式的类图如下:

抽象构件角色:

package com.charon.composite.transparent;

/**
 * @className: Graphics
 * @description: 抽象构件角色
 * @author: charon
 * @create: 2022-03-21 20:09
 */
public abstract class Graphics {
    /**
     * 绘制方法
     */
    abstract void draw();

    /**
     * 增加一个子构建对象
     */
    abstract void add();

    /**
     * 删除一个子构件对象
     */
    abstract void remove();

    /**
     * 返回一个子构建对象
     * @param i
     * @return
     */
    abstract Graphics getChild(int i);
}

树枝构件角色:

package com.charon.composite.transparent;

import java.util.Vector;

/**
 * @className: Picture
 * @description: 树枝构件角色
 * @author: charon
 * @create: 2022-03-21 19:40
 */
public class Picture extends Graphics {

    private Vector items = new Vector(10);

    /**
     * 具体管理方法,增加一个子构件对象
     * @param graphics
     */
    public void add(Graphics graphics){
        items.add(graphics);
    }

    /**
     * 删除一个子构件对象
     * @param graphics
     */
    public void remove(Graphics graphics){
        items.remove(graphics);
    }

    /**
     * 返回一个子构件对象
     * @param i
     * @return
     */
    public .Graphics getChild(int i){
        return (Graphics) items.get(i);
    }

    @Override
    public void draw() {
        for (int i = 0; i < items.size(); i++) {
            Graphics graphics = (Graphics) items.get(i);
            graphics.draw();
        }
    }
}

树叶构件角色:

package com.charon.composite.transparent;

/**
 * @className: Line
 * @description:
 * @author: charon
 * @create: 2022-03-21 20:15
 */
public class Line extends Graphics{
    @Override
    void draw() {
        System.out.println("画了一条线。。。。");
    }

    @Override
    void add() {

    }

    @Override
    void remove() {

    }

    @Override
    Graphics getChild(int i) {
        return null;
    }
}

package com.charon.composite.transparent;

/**
 * @className: Circle
 * @description:
 * @author: charon
 * @create: 2022-03-21 20:16
 */
public class Circle extends Graphics{
    @Override
    void draw() {
        System.out.println("画了一个圆形。。。。");
    }

    @Override
    void add() {

    }

    @Override
    void remove() {

    }

    @Override
    Graphics getChild(int i) {
        return null;
    }
}
package com.charon.composite.transparent;

/**
 * @className: Rectangle
 * @description:
 * @author: charon
 * @create: 2022-03-21 20:16
 */
public class Rectangle extends Graphics{
    @Override
    void draw() {
        System.out.println("画了一个长方形。。。。。");
    }

    @Override
    void add() {

    }

    @Override
    void remove() {

    }

    @Override
    Graphics getChild(int i) {
        return null;
    }
}

测试:

package com.charon.composite.transparent;

import com.charon.composite.transparent.Circle;
import com.charon.composite.transparent.Line;
import com.charon.composite.transparent.Rectangle;

/**
 * @className: Client
 * @description:
 * @author: charon
 * @create: 2022-03-21 20:17
 */
public class Client {
    public static void main(String[] args) {
        Picture picture = new Picture();
        Circle circle = new Circle();
        picture.add(circle);
        picture.add(new Rectangle());
        picture.add(new Line());
        picture.add(new Rectangle());
        picture.remove(circle);
        picture.draw();
    }
}

打印:
    画了一个长方形。。。。
    画了一条线。。。。
    画了一个长方形。。。。

安全式

第二种选择是在 Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。编译通不过,就不会出现运行时期错误。

这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。

安全式的组合模式要求管理具体的方法只出现在树枝构件类中,如下图所示:

安全式组合模式涉及到抽象构件角色、树叶构件角色、树枝构件角色这三个角色:

  • 抽象构件角色(Component):这是一个抽象角色,他给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。组合对象通常把它所包含的子对象当作类型为component的对象,在安全式的组合模式里,构件角色并不定义出管理子对象的方法
  • 树叶构件角色(Leaf):树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为
  • 树枝构件角色(Composite):代表参加组合的有下级子对象的对象,树枝构件类给出所有的管理子对象的方法,如add(),remove()以及getChild()等方法

同样以上面的绘图系统为例子讲解安全式组合模式。安全式组合模式意味着只有树枝构件角色才能配备有管理聚集的方法,而树叶构件角色则没有这些方法。UML类图如下:

抽象构件角色:

package com.charon.composite.safe;

/**
 * @className: Graphics
 * @description: 抽象构件角色
 * @author: charon
 * @create: 2022-03-21 19:38
 */
public abstract class Graphics {
    public abstract void draw();
}

树枝构件角色:

package com.charon.composite.safe;

import java.util.Vector;

/**
 * @className: Picture
 * @description: 树枝构件角色
 * @author: charon
 * @create: 2022-03-21 19:40
 */
public class Picture extends Graphics{

    private Vector items = new Vector(10);

    /**
     * 具体管理方法,增加一个子构件对象
     * @param graphics
     */
    public void add(Graphics graphics){
        items.add(graphics);
    }

    /**
     * 删除一个子构件对象
     * @param graphics
     */
    public void remove(Graphics graphics){
        items.remove(graphics);
    }

    /**
     * 返回一个子构件对象
     * @param i
     * @return
     */
    public Graphics getChild(int i){
        return (Graphics) items.get(i);
    }

    @Override
    public void draw() {
        for (int i = 0; i < items.size(); i++) {
            Graphics graphics = (Graphics) items.get(i);
            graphics.draw();
        }
    }
}

树叶构件角色:

package com.charon.composite.safe;

/**
 * @className: Line
 * @description: 直线
 * @author: charon
 * @create: 2022-03-21 19:46
 */
public class Line extends Graphics {
    @Override
    public void draw() {
        System.out.println("画了一条线。。。。");
    }
}

package com.charon.composite.safe;

/**
 * @className: Circle
 * @description: 圆
 * @author: charon
 * @create: 2022-03-21 19:47
 */
public class Circle extends Graphics{
    @Override
    public void draw() {
        System.out.println("画了一个圆。。。。。");
    }
}

package com.charon.composite.safe;

/**
 * @className: Rectangle
 * @description: 长方形
 * @author: charon
 * @create: 2022-03-21 19:47
 */
public class Rectangle extends Graphics{
    @Override
    public void draw() {
        System.out.println("画了一个长方形。。。。");
    }
}

测试:

package com.charon.composite.safe;

/**
 * @className: Client
 * @description:
 * @author: charon
 * @create: 2022-03-21 19:49
 */
public class Client {

    public static void main(String[] args) {
        Picture picture = new Picture();
        Circle circle = new Circle();
        picture.add(circle);
        picture.add(new Rectangle());
        picture.add(new Line());
        picture.add(new Rectangle());
        picture.remove(circle);
        picture.draw();
    }
}

打印:
    画了一个长方形。。。。
    画了一条线。。。。
    画了一个长方形。。。。

组合模式的应用场景

  1. 在需要表示一个对象整体与部分的层次结构的场合。
  2. 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
posted @ 2022-03-21 21:20  pluto_charon  阅读(320)  评论(0编辑  收藏  举报