合成(Composite)模式

  合成(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对象,具体的类型对于客户端而言是透明的,是无须关心的。
  对于合成模式而言,在安全性和透明性上,会更看重透明性,毕竟合成模式的目的是:让客户端不再区分操作的是树枝对象还是树叶对象,而是以一个统一的方式来操作。

  而且对于安全性的实现,需要区分是树枝对象还是树叶对象。有时候,需要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的。

  因此在使用合成模式的时候,建议多采用透明性的实现方式。

原文地址:https://www.cnblogs.com/qlqwjy/p/11049493.html