结构篇-组合模式


组合模式(Composite)是针对由多个节点对象(部分)组成的树形结构的对象(整体)而发展出的一种结构型设计模式,它能够使客户端在操作整体对象或者其下的每个节点对象时做出统一的响应,保证树形结构对象使用方法的一致性,使客户端不必关注对象的整体或部分,最终达到对象复杂的层次结构与客户端解耦的目的。


提示:以下是本篇文章正文内容,下面案例可供参考

一、叉树结构

在现实世界中,某些具有从属关系的事物之间存在着一定的相似性。大家一定见过蕨类植物的叶子吧。从宏观上看,这只是一片普通的叶子,当继续观察其中一个分支的时候,我们会发现这个分支其实又是一片全新的叶子,当我们再继续观察这片新叶子的一个分支的时候,又会得到相同的结果。

不管是二叉树还是多叉树,道理都是一样的。无论数据元素是“根”“枝”,还是“叶”,甚至是整体的树,都具有类似的结构。具体来讲,除了叶节点没有子节点,其他节点都具有本级对象包含多个次级子对象的结构特征。所以,我们完全没有必要为每个节点对象定义不同的类(如为字、词、句、段、节、章……等每个节点都定义一个类),否则会造成代码冗余。我们可以用组合模式来表达“部分/整体”的层次结构,提取并抽象其相同的部分,特殊化其不同的部分,以提高系统的可复用性与可扩展性,最终达到以不变应万变的目的。

二、文件系统

通过对叉树结构的观察,我们发现,无论拿出哪一个“部分”,其与“整体”的结构都是类似的,所以首先我们需要模糊根、枝、叶之间的差异,以实现节点的统一。下面开始代码实战部分,我们就以类似于树结构的文件系统的目录结构为例。

1. 抽象节点类Node

文件系统从根目录“C:”开始分支,其下级可以包含“文件夹”或者“文件”,其中文件夹属于“枝”节点,其下级可以继续存放子文件夹或文件,而文件则属于“叶”节点,其下级不再有任何子节点。基于此前的分析,我们可以定义一个抽象的“节点”类来模糊“文件夹”与“文件”。

public abstract class Node {
    // 节点命名
    protected String name;

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

    //添加下级子节点方法
    protected abstract void add(Node child);
}

说明:

  1. 文件夹或文件都有一个名字,所以在构造方法中接收并初始化已定义的节点名,否则不允许节点被创建,这也是可以固化下来的逻辑。对于如何实现代码添加子节点方法add(Node child)暂时还不能确定,所以我们声明其为抽象方法,模糊此行为并留给子类去实现。需要注意的是,对于抽象节点类Node的抽象方法其实还可以更加丰富,例如“删除节点”“获取节点”等,这里为了简化代码只声明了“添加节点”方法。

2.文件夹类

public class Folder extends Node{
    // 文件夹可以包含子节点(子文件夹或者文件)
    private List<Node> childrenNodes = new ArrayList<>();

    public Folder(String name) {
        super(name);
    }

    @Override
    protected void add(Node child) {
        // 可以添加子节点
        childrenNodes.add(child);
    }
}

说明:

  1. 首先,文件夹类继承了抽象节点类Node,并在第3行定义了一个次级节点列表List,此处的泛型Node既可以是文件夹又可以是文件,也就是说,文件夹下级可以包含任意多个文件夹或者文件。

3.文件类

public class File extends Node{

    public File(String name) {
        super(name);
    }

    @Override
    protected void add(Node child) {
        System.out.println("不能添加子节点");
    }
}

说明:

  1. 除了添加子节点方法add(Node child),文件类与文件夹类的代码大同小异。如之前提到的,文件属于“叶”节点,不能再将这种结构延续下去,所以我们在方法输出一个错误消息,告知用户“不能添加子节点”。其实更好的方式是以抛出异常的形式来确保此处逻辑的正确性,外部如果捕获到该异常则可以做出相应的处理。

4.客户端类

public class Client {
    public static void main(String[] args) {
        Node d = new Folder("D盘");

        Node doc = new Folder("文档");
        doc.add(new File("简历.doc"));
        doc.add(new File("项目介绍.ppt"));

        d.add(doc);

        Node music = new Folder("音乐");

        Node jay = new Folder("周杰伦");
        jay.add(new File("轨迹.mp3"));
        jay.add(new File("说好不哭.mp3"));
        jay.add(new File("半岛铁盒.mp3"));

        Node eason = new Folder("陈奕迅");
        eason.add(new File("听阴天说什么"));
        eason.add(new File("淘汰"));

        music.add(jay);
        music.add(eason);

        d.add(music);

    }
}

说明:

  1. 正如我们规划文件时常做的操作,用户以“D盘”文件夹作为根节点构建了目录树,接着创建了“文档”和“音乐”两个文件夹作为“枝”节点,再将相应类型的文件分别置于相应的目录下,其中对音乐文件多加了一级文件夹来区分歌手,以便日后分类管理、查找。如此一来,只要能持有根节点对象“D盘”,就能延伸出整个目录。

三、目录树展示

1.添加展示方法

目录树虽已构建完成,但要体现出组合模式的优势还在于如何运用这个树结构。假如用户现在要查看当前根目录下的所有子目录及文件,这就需要分级展示整棵目录树,正如Windows系统的“tree”命令所实现的。要模拟这种树形展示方式,我们就得在输出节点名称(文件夹名/文件名)之前加上数个空格以表示不同层级,但具体加几个空格还是个未知数,需要根据具体的节点级别而定。而作为抽象节点类则不应考虑这些细节,而应先把这个未知数作为参数变量传入,我们来修改抽象节点类Node并加入展示方法,

public abstract class Node {
    // 节点命名
    protected String name;

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

    //添加下级子节点方法
    protected abstract void add(Node child);

    protected void tree(int space) {
        for (int i = 0; i < space; i++) {
            //先循环输出space个空格
            System.out.print(" ");
        }
        //接着再输出自己的名字
        System.out.println(name);
    }

    protected void tree(){
        this.tree(0);
    }
}

说明:

  1. 空格偏移量这个必传参数可能让用户非常困惑,或许我们可以为抽象节点类添加一个无参的展示方法“tree()”,在其内部调用“tree(0)”,如此一来就不再需要用户传入偏移量了,使用起来更加方便。
  2. 作为末端节点的文件类只需要输出space个空格再加上自己的名称即可,所以文件类直接继承使用父类的展示方法,不用改写。

2.改写文件夹类

文件夹类就比较特殊了,它不仅要先输出自己的名字,还要换行再逐个输出子节点的名字,并且要保证空格逐级递增。

public class Folder extends Node {
    // 文件夹可以包含子节点(子文件夹或者文件)
    private List<Node> childrenNodes = new ArrayList<>();

    public Folder(String name) {
        super(name);
    }

    @Override
    protected void add(Node child) {
        // 可以添加子节点
        childrenNodes.add(child);
    }

    @Override
    protected void tree(int space) {
        // 调用父类通用的tree
        super.tree(space);
        // 在循环子节点前,空格数要加1
        space++;
        for (Node childrenNode : childrenNodes) {
            childrenNode.tree(space);
        }
    }
}

说明:

  1. 文件夹类重写并覆盖了父类的tree()方法,首先调用父类的通用tree()方法输出本文件夹的名字。接下来的逻辑就非常有意思了,对于下一级的子节点我们需要依次输出,但前提是要把当前的空格数加1,如此一来子节点的位置会往右偏移一格,这样才能看起来像树形结构一样错落有致。可以看到,在循环体中我们直接调用了子节点的展示方法并把“加1”后的空格数传递给它即可完成展示。至于当前文件夹下的子节点到底是“文件夹”还是“文件”,我们完全不必操心,因为子节点们会使用自己的展示逻辑。如果它们还有下一级子节点,则与此处逻辑相同,继续循环,把逐级递增的空格数传递下去,直至抵达叶节点为止——始于“文件夹”而终于“文件”,非常完美的递归逻辑。

3.输出结果

直接在客户端中的D盘文件夹调用tree()方法及可

public class Client {
    public static void main(String[] args) {
        Node d = new Folder("D盘");
        ...
        ...
        ...
        d.tree();

    }
}
输出结果:
D盘
 文档
  简历.doc
  项目介绍.ppt
 音乐
  周杰伦
   轨迹.mp3
   说好不哭.mp3
   半岛铁盒.mp3
  陈奕迅.mp3
   听阴天说什么.mp3
   淘汰.mp3

总结

提示:这里对文章进行总结:

  1. 组合模式将树形结构的特点发挥得淋漓尽致,作为最高层级抽象的抽象节点类(接口)泛化了所有节点类,使任何“整体”或“部分”达成统一,枝(根)节点与叶节点的多态化实现以及组合关系进一步勾勒出的树形结构,最终使用户操作一触即发,由“根”到“枝”再到“叶”,逐级递归,自动生成。
  2. 抽象工厂方法模式的各角色定义如下。
  • Component(组件接口):所有复合节点与叶节点的高层抽象,定义出需要对组件操作的接口标准。对应本章例程中的抽象节点类,具体使用接口还是抽象类需根据具体场景而定。
  • Composite(复合组件):包含多个子组件对象(可以是复合组件或叶端组件)的复合型组件,并实现组件接口中定义的操作方法。对应本章例程中作为“根节点/枝节点”的文件夹类。
  • Leaf(叶端组件):不包含子组件的终端组件,同样实现组件接口中定义的操作方法。对应本章例程中作为“叶节点”的文件类。
  • Client(客户端):按所需的层级关系部署相关对象并操作组件接口所定义的接口,即可遍历树结构上的所有组件。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. 附加例及代码 包含教材各章的10个补充实例及完整的Java源程序代码,具体如下。  【附加例3.1】利用类适配器进行设计的邮政编码检验系统  【附加例3.2】利用对象适配器设计的关于椭圆的不同画法的程序  【附加例3.3】利用外观模式设计的学生信息文档  【附加例3.4】利用桥接模式设计的特工信息保密系统  【附加例4.1】利用中介者模式进行重构的实例  【附加例4.2】利用策略模式设计的相同数据的不同图表显示的实例  【附加例4.3】利用状态模式设计的天气状态软件  【附加例4.4】利用状态模式设计的中国个人所得税的计算系统  【附加例6.1】二手车拍卖系统最简单的设计与实现  【附加例6.2】二手车拍卖系统的非MVC设计与实现-两个类的情况 2. 教材各章实例代码 包括书中各章实例的Java源程序代码 (共46组),列表如下。 (1)上:软件设计模式例子代码  【例2.2】简单工厂方法模式-汽车保险  【例2.3】工厂方法模式-汽车保险  【例2.4】抽象工厂模式-房屋信息  【例2.5】生成器模式-房屋信息  【例2.6】单例模式-互联网连接  【例3.2】组合模式-五子棋代码  【例3.3】组合模式-空军指挥系统  【例3.4】组合模式-世界问候语  【例3.7】类适配器模式-客户信息验证  【例3.8】对象适配器模式-字符串排序  【例3.10】外观模式-安全系统  【例3.11】外观模式-椭圆功能  【例3.13】桥接模式-茶水机系统  【例3.14】桥接模式-几何立体体积  【例4.1】迭代器模式-矩阵搜索  【例4.2】迭代器模式-产品搜索  【例4.4】访问者模式-名牌鞋销售软件  【例4.5】访问者模式-计算机部件销售软件  【例4.6】命令模式-室内温度控制  【例4.7】命令模式-室内温度控制-2个GUI  【例4.8】命令模式-室内温度控制-3个GUI  【例4.10】中介者模式-旅游信息共享  【例4.11】中介者模式-海岛机场  【例4.13】策略模式-整数排序  【例4.14】策略模式-中国属相  【例4.16】状态模式-交通信号灯-设计1  【例4.16】状态模式-交通灯信号灯-设计2  【例4.16】状态模式-交通灯信号灯-设计3 (2)下:软件体系结构例子代码  【例6.4】结构化设计-文件更新-C源代码  【例6.5】面向对象设计架构-文件更新  【例6.7】顺序批处理架构-文件更新  【例6.8】顺序批处理架构-图像处理  【例6.9】管道过滤器架构-主动过滤器  【例6.10】管道过滤器架构-被动过滤器  【例6.11】管道-过滤器架构-文件更新  【例6.12】管道-过滤器架构-图像处理程  【例6.14】事件体系结构-鼠标响应  【例6.17】事件体系结构-观察者模式-大草原1  【例6.18】事件体系结构-观察者模式-大草原2  【例6.19】事件体系结构-观察者模式-温度显示  【例6.21】层次架构-软件测试  【例6.22】层次架构-银行- Access数据库  【例6.23】MVC架构-二手车拍卖-无观察者  【例6.24】MVC架构-二手车拍卖-观察者-3个图形界面  【例6.25】MVC架构-二手车拍卖-观察者-1个图形界面  2.3.3节中应用单例模式设计的President程序 3. 软件设计-编程作业 共包含25个作业,每个作业都有部分可运行代码。在每次做作业之前请先看相应文件夹中的Word文档(包含描述该作业的类图),这样才能很好地理解作业中的源代码。通常每个作业都要求学生在原有的设计中增加新的单独的类或层次类,并且要求具体实现其设计。具体如下。 (1)上:软件设计模式  【作业2.1-1】-工厂方法模式-汽车保险  【作业2.1-2】-抽象工厂模式-房屋信息  【作业2.2-1】-生成器模式-房屋信息  【作业2.3-1】-单例模式-网络连接  【作业3.1-1】-组合模式-空军指挥  【作业3.2-1】-适配器模式-客户信息验证  【作业3.2-2】-适配器模式-邮编验证  【作业3.3-1】-外观模式-毕业生信息  【作业3.4-1】-桥接模式-几何体积  【作业3.4-2】-桥接模式-特工信息  【作业4.1-1】-迭代器模式-矩阵型数据搜索  【作业4.2-1】-访问者模式-名牌鞋查询  【作业4.2-2】-访问者模式-计算机配件  【作业4.3-1】-命令模式-空调  【作业4.3-2】-命令模式-空调2  【作业4.4-1】-中介者模式-商业信息共享  【作业4.5-1】-策略模式-排序  【作业4.5-2】-策略模式-属相  【作业4.6-1】-状态模式-天气  【作业4.6-2】-状态模式-税收 (2)下:软件体系结构  【作业6.1-1】-状态模式-税收  【作业6.2-1】-管道-过滤器  【作业6.3-1】-观察者  【作业6.4-1】-层次架构 【作业6.5-1】-MVC
:软件设计模式例子代码  【例2.2】简单工厂方法模式-汽车保险  【例2.3】工厂方法模式-汽车保险  【例2.4】抽象工厂模式-房屋信息  【例2.5】生成器模式-房屋信息  【例2.6】单例模式-互联网连接  【例3.2】组合模式-五子棋代码  【例3.3】组合模式-空军指挥系统  【例3.4】组合模式-世界问候语  【例3.7】类适配器模式-客户信息验证  【例3.8】对象适配器模式-字符串排序  【例3.10】外观模式-安全系统  【例3.11】外观模式-椭圆功能  【例3.13】桥接模式-茶水机系统  【例3.14】桥接模式-几何立体体积  【例4.1】迭代器模式-矩阵搜索  【例4.2】迭代器模式-产品搜索  【例4.4】访问者模式-名牌鞋销售软件  【例4.5】访问者模式-计算机部件销售软件  【例4.6】命令模式-室内温度控制  【例4.7】命令模式-室内温度控制-2个GUI  【例4.8】命令模式-室内温度控制-3个GUI  【例4.10】中介者模式-旅游信息共享  【例4.11】中介者模式-海岛机场  【例4.13】策略模式-整数排序  【例4.14】策略模式-中国属相  【例4.16】状态模式-交通信号灯-设计1  【例4.16】状态模式-交通灯信号灯-设计2  【例4.16】状态模式-交通灯信号灯-设计3 下:软件体系结构例子代码  【例6.4】结构化设计-文件更新-C源代码  【例6.5】面向对象设计架构-文件更新  【例6.7】顺序批处理架构-文件更新  【例6.8】顺序批处理架构-图像处理  【例6.9】管道过滤器架构-主动过滤器  【例6.10】管道过滤器架构-被动过滤器  【例6.11】管道-过滤器架构-文件更新  【例6.12】管道-过滤器架构-图像处理程  【例6.14】事件体系结构-鼠标响应  【例6.17】事件体系结构-观察者模式-大草原1  【例6.18】事件体系结构-观察者模式-大草原2  【例6.19】事件体系结构-观察者模式-温度显示  【例6.21】层次架构-软件测试  【例6.22】层次架构-银行- Access数据库  【例6.23】MVC架构-二手车拍卖-无观察者  【例6.24】MVC架构-二手车拍卖-观察者-3个图形界面  【例6.25】MVC架构-二手车拍卖-观察者-1个图形界面

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zhixuChen200

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

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

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

打赏作者

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

抵扣说明:

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

余额充值