设计模式之组合模式

概念

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

合成模式

合成模式把部分和整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由它们复合而成的合成对象同等看待。

比如,一个文件系统就是一个典型的合成模式系统。下图是常见的计算机XP文件系统的一部分。
在这里插入图片描述
从上图可以看出,文件系统是一个树结构,树上长有节点。树的节点有两种,一种是树枝节点,即目录,有内部树结构,在图中涂有颜色;另一种是文件,即树叶节点,没有内部树结构。
  显然,可以把目录和文件当做同一种对象同等对待和处理,这也就是合成模式的应用。
  合成模式可以不提供父对象的管理方法,但是合成模式必须在合适的地方提供子对象的管理方法,诸如:add()、remove()、以及getChild()等。
  合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全式透明式

安全式合成模式的结构

安全模式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件类中。
在这里插入图片描述
这种形式涉及到三个角色:

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

源代码

/**
 * 抽象构建,使用接口来定义,只定义出叶子节点和树枝节点共同的方法
 */
public interface Component {

    /**
     * 展示节点名称
     */
    public void showName(String parentName);

}
/**
 * 叶子节点
 */
public class Leaf implements Component {

    //节点名称
    private String name;

    public Leaf(String name) {
        this.name = name;
    }

    @Override
    public void showName(String parentName) {
        System.out.println(parentName + "-" + name);
    }
}
/**
 * 树枝节点,实现了Component接口,除了实现接口方法外,还有自己的管理子节点的方法
 */
public class Composite implements Component {

    //节点名称
    private String name;

    public Composite(String name) {
        this.name = name;
    }

    //子节点
    private List<Component> childList = new ArrayList<Component>();

    //添加节点
    public void addChild(Component child) {
        childList.add(child);
    }

    //删除节点
    public void removeChild(int index) {
        childList.remove(index);
    }

    @Override
    public void showName(String parentName) {
        System.out.println(parentName + "-" + name);
        if(this.childList != null && this.childList.size() > 0){
            for(Component component: this.childList){
                component.showName(parentName + "-" + name);
            }
        }
    }
}
/**
 * 客户端
 */
public class Client {

    public static void main(String[] args) {

        //树枝节点必须指明对象为Composite,而不能是接口对象,因为接口对象没有方法操作子节点
        Composite root = new Composite("服装");
        Composite boy = new Composite("男装");
        Composite girl = new Composite("女装");

        Component boy1 = new Leaf("短袖");
        Component boy2 = new Leaf("沙滩裤");

        Component girl1 = new Leaf("胸罩");
        Component girl2 = new Leaf("内裤");

        boy.addChild(boy1);
        boy.addChild(boy2);

        girl.addChild(girl1);
        girl.addChild(girl2);

        root.addChild(boy);
        root.addChild(girl);

        root.showName("");
    }

}

可以看出,树枝构件类(Composite)给出了addChild()、removeChild()以及getChild()等方法的声明和实现,而树叶构件类则没有给出这些方法的声明或实现。这样的做法是安全的做法,由于这个特点,客户端应用程序不可能错误地调用树叶构件的聚集方法,因为树叶构件没有这些方法,调用会导致编译错误。

安全式合成模式的缺点是不够透明,因为树叶类和树枝类将具有不同的接口。

透明式合成模式的结构

与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定接口。
在这里插入图片描述
源代码

/**
 * 抽象构建,使用抽象类来实现,子节点和树枝节点共有的方法用抽象方法定义,
 * 树枝节点独有的方法用普通方法实现,并给出缺省实现
 */
public abstract class Component {

    /**
     * 添加子节点
     * @param component
     */
    public void addChild(Component component) {
        /**
         * 缺省实现,抛出异常,因为叶子对象没有此功能
         * 或者子组件没有实现这个功能
         */
        throw new UnsupportedOperationException("对象不支持此方法");
    }

    /**
     * 删除子节点
     * @param index
     */
    public void removeChild(int index) {
        /**
         * 缺省实现,抛出异常,因为叶子对象没有此功能
         * 或者子组件没有实现这个功能
         */
        throw new UnsupportedOperationException("对象不支持此方法");
    }

    /**
     * 展示节点名称
     */
    public abstract void showName(String parentName);

}
/**
 * 叶子节点
 */
public class Leaf extends Component {

    //节点名称
    private String name;

    public Leaf(String name) {
        this.name = name;
    }

    @Override
    public void showName(String parentName) {
        System.out.println(parentName + "-" + name);
    }

}
/**
 * 树枝节点,继承抽象类,重写方法
 */
public class Composite extends Component {

    //节点名称
    private String name;

    public Composite(String name) {
        this.name = name;
    }

    //子节点
    private List<Component> childList = new ArrayList<Component>();

    //添加节点
    @Override
    public void addChild(Component child) {
        childList.add(child);
    }

    //删除节点
    @Override
    public void removeChild(int index) {
        childList.remove(index);
    }

    @Override
    public void showName(String parentName) {
        System.out.println(parentName + "-" + name);
        if(this.childList != null && this.childList.size() > 0){
            for(Component component: this.childList){
                component.showName(parentName + "-" + name);
            }
        }
    }
}
/**
 * 客户端
 */
public class Client {

    public static void main(String[] args) {
        //由于是透明式组合模式,所有方法在抽象类中均有定义,所以所有的类型都可以为抽象类
        Component root = new Composite("服装");
        Component boy = new Composite("男装");
        Component girl = new Composite("女装");

        Component boy1 = new Leaf("短袖");
        Component boy2 = new Leaf("沙滩裤");

        Component girl1 = new Leaf("胸罩");
        Component girl2 = new Leaf("内裤");

        boy.addChild(boy1);
        boy.addChild(boy2);

        girl.addChild(girl1);
        girl.addChild(girl2);

        root.addChild(boy);
        root.addChild(girl);

        root.showName("");
    }

}

可以看出,客户端无需再区分操作的是树枝对象(Composite)还是树叶对象(Leaf)了;对于客户端而言,操作的都是Component对象。

两种实现方法的选择

这里所说的安全性合成模式是指:从客户端使用合成模式上看是否更安全,如果是安全的,那么就不会有发生误操作的可能,能访问的方法都是被支持的。

这里所说的透明性合成模式是指:从客户端使用合成模式上,是否需要区分到底是“树枝对象”还是“树叶对象”。如果是透明的,那就不用区分,对于客户而言,都是Compoent对象,具体的类型对于客户端而言是透明的,是无须关心的。

对于合成模式而言,在安全性和透明性上,会更看重透明性,毕竟合成模式的目的是:让客户端不再区分操作的是树枝对象还是树叶对象,而是以一个统一的方式来操作。

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

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

关注公众号【程序员每日一学】让我们每天一起学习进步~

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿小张丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值