设计模式之组合模式(4)

组合模式 (Composite Pattern)

目录

模式动机

类图

参与者

使用场景

优点

缺点:

例子


模式动机

组合多个对象形成树形结构以表示“整体—部分”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。组合模式又可以称为“整体—部分”(Part-Whole)模式,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体与部分的关系。

类图

参与者

● Component 抽象构件角色
定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。

● Leaf 叶子构件
叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。

● Composite 树枝构件
树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。

使用场景

在以下情况下适合使用组合模式:

  • 需要表示一个对象整体或部分层次。
  • 想让客户能够忽略不同对象层次的变化。
  • 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们。

优点

  • 清楚地定义分层次的负责对象,表示对象的全部或部分层次
  • 让客户端忽略了层次的差异,方便对整个层次结构进行控制
  • 简化客户端的代码
  • 符合开闭原则

缺点:

  • 限制类型时会比较复杂
  • 使设计变得更加抽象

合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全式和透明式。

安全式:在Composite类中定义管理子部件的方法具有良好的安全性,因为在C++这样的静态类型语言中,在编译时任何从Leaf中增加或删除对象的尝试都将被发现。但是这又损失了透明性,因为Leaf和Composite显然具有不同的接口。

透明式:在类层级结构的根部定义子节点管理接口的方法具有良好的透明性,因为你可以一致地使用所有的组件,但是这一方法以安全性为代价,因为客户有可能会做一些无意义的事情,例如在Leaf中增加和删除对象。

例子

安全式合成模式
安全模式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件类中

安全式合成模式代码:

/**
 * 抽象构件角色类
 */
public interface Component {

    /**
     * 输出组件的名称
     * @param preStr
     */
    void printStruct(String preStr);

}
/**
 * 树枝构件角色类(目录)
 */
public class Composite implements Component {

    /**
     * 存储组合对象中包含的子组件的对象(可能是目录也可能是文件)
     */
    private List<Component> childComponents = new ArrayList<>();

    /**
     * 组合对象的名称
     */
    private String name;

    /**
     * 构件名称
     * @param name
     */
    public Composite(String name) {
        this.name = name;
    }


    /**
     * 输出对象的自身结构
     * @param preStr 前缀,主要是按照层级拼接空格,实现向后缩进
     */
    @Override
    public void printStruct(String preStr) {
        // 先把自己输出
        System.out.println(preStr + "+" + this.name);
        //如果还包含有子组件,那么就输出这些子组件对象
        if(this.childComponents != null){
            //添加两个空格,表示向后缩进两个空格
            preStr += "  ";
            //输出当前对象的子对象
            for(Component c : childComponents){
                //递归输出每个子对象
                c.printStruct(preStr);
            }
        }
    }

    /**
     * 聚集管理方法,增加一个子构件对象
     * @param child 子构件对象
     */
    public void addChild(Component child){
        childComponents.add(child);
    }

    /**
     * 聚集管理方法,删除一个子构件对象
     * @param index 子构件对象的下标
     */
    public void removeChild(int index){
        childComponents.remove(index);
    }

    /**
     * 聚集管理方法,返回所有子构件对象
     */
    public List<Component> getChild(){
        return childComponents;
    }
}
/**
 * 叶子构件
 */
public class Leaf implements Component {

    /**
     * 叶子对象的名字
     */
    private String name;
    /**
     * 构造方法,传入叶子对象的名称
     * @param name 叶子对象的名字
     */
    public Leaf(String name){
        this.name = name;
    }
    /**
     * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
     * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进
     */
    @Override
    public void printStruct(String preStr) {
        System.out.println(preStr + "-" + name);
    }
}
/**
 * 安全模式(客户端)
 */
public class Client {

    public static void main(String[] args) {
        Composite root = new Composite("D盘");
        Composite favoriteDir = new Composite("收藏夹");
        Composite picDir = new Composite("图片");
        picDir.addChild(new Leaf("美女1.png"));
        picDir.addChild(new Leaf("美女2.png"));		

        Composite workDir = new Composite("工作");
        workDir.addChild(new Leaf("需求文档.doc"));
        Composite learningDir = new Composite("学习");
        learningDir.addChild(new Leaf("Java笔记.md"));
        learningDir.addChild(new Leaf("数据库笔记.doc"));

        root.addChild(favoriteDir);
        root.addChild(workDir);
        root.addChild(learningDir);
        root.addChild(picDir);
        root.addChild(new Leaf("log.txt"));
        
        //错误演示
        //Leaf leaf = new Leaf("test.txt");
        //leaf.addChild(new Leaf("log.txt"))//报错,编译不通过,安全模式下不会出现错误调用
        
		//输出整个目录结构
        root.printStruct("");

    }

}


 

可以看出,树枝构件类(Composite)给出了addChild()、removeChild()以及getChild()等方法的声明和实现,而树叶构件类则没有给出这些方法的声明或实现。这样的做法是安全的,由于这个特点,客户端应用程序不可能错误地调用树叶构件的聚集方法,因为树叶构件没有这些方法,调用会导致编译错误。但是,这种安全模式却有违背组合模式原本的定义,使客户端对单个对象和组合对象保持一致的方式处理

透明式合成模式
与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定接口


透明合成模式代码

/**
 * 抽象构件角色类
 */
public abstract class Component {

    /**
     * 输出组件自身的名称
     */
    public abstract void printStruct(String preStr);

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

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

    /**
     * 聚集管理方法,返回所有子构件对象
     */
    public List<Component> getChild(){
        /**
         * 缺省实现,抛出异常,因为叶子对象没有此功能
         * 或者子组件没有实现这个功能
         */
        throw new UnsupportedOperationException("对象不支持此功能");
    }
}
/**
 * 树枝构件角色类,此类将implements Conponent改为extends Conponent,其他地方无变化
 */
public class Composite extends Component {

    /**
     * 用来存储组合对象中包含的子组件对象
     */
    private List<Component> childComponents = new ArrayList<Component>();

    /**
     * 组合对象的名字
     */
    private String name;

    /**
     * 构造方法,传入组合对象的名字
     * @param name    组合对象的名字
     */
    public Composite(String name){
        this.name = name;
    }

    /**
     * 聚集管理方法,增加一个子构件对象
     * @param child 子构件对象
     */
    public void addChild(Component child){
        childComponents.add(child);
    }

    /**
     * 聚集管理方法,删除一个子构件对象
     * @param index 子构件对象的下标
     */
    public void removeChild(int index){
        childComponents.remove(index);
    }

    /**
     * 聚集管理方法,返回所有子构件对象
     */
    public List<Component> getChild(){
        return childComponents;
    }

    /**
     * 输出对象的自身结构
     * @param preStr 前缀,主要是按照层级拼接空格,实现向后缩进
     */
    @Override
    public void printStruct(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 extends Component {
    /**
     * 叶子对象的名字
     */
    private String name;
    /**
     * 构造方法,传入叶子对象的名称
     * @param name 叶子对象的名字
     */
    public Leaf(String name){
        this.name = name;
    }
    /**
     * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
     * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进
     */
    @Override
    public void printStruct(String preStr) {
        System.out.println(preStr + "-" + name);
    }

}
/**
 * 透明模式(客户端)
 */
public class Client {

    public static void main(String[] args) {
        Component root = new Composite("D盘");
        Component favoriteDir = new Composite("收藏夹");
        Component picDir = new Composite("图片");
        picDir.addChild(new Leaf("美女1.png"));
        picDir.addChild(new Leaf("美女2.png"));


        Component workDir = new Composite("工作");
        workDir.addChild(new Leaf("需求文档.doc"));
        Component learningDir = new Composite("学习");
        learningDir.addChild(new Leaf("Java笔记.md"));
        learningDir.addChild(new Leaf("数据库笔记.doc"));

        root.addChild(favoriteDir);
        root.addChild(workDir);
        root.addChild(learningDir);
        root.addChild(picDir);
        root.addChild(new Leaf("log.txt"));

        //Component leaf = new Leaf("test");
        //leaf.addChild(new Leaf("log.txt"));//编译通过,运行报错

        root.printStruct("");

    }

}


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

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

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

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

而且对于安全性的实现,需要区分是树枝对象还是树叶对象。有时候,需要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的。因此在使用合成模式的时候,建议多采用透明性的实现方式


参考:https://blog.csdn.net/u014727260/article/details/82722473 
 

猜你喜欢

转载自blog.csdn.net/qq_36986067/article/details/90217669