树形结构魔法——组合模式

 

📚 在计算机的文件系统中,我们有文件,也有文件夹,文件我们又有音乐类型的文件、视频类型的文件、图片类型的文件和文本类型文件等,文件夹中可以包含文件,也可以包含文件夹。如果A公司需要写一个服务器文件管理系统,系统需要列出服务器中的每个文件和文件夹信息展示,而且对文件进行管理。

🙋‍♂️ 类似于对于这种管理属性结构数据的需求,比如管理文件系统中的文件和文件夹、公司组织机构中的部门等,我们都可以使用组合模式来实现相关的功能。

1. 概述

组合模式将对象组合成树形结构以表示"部分-整体"的层次结构,使客户端对单个对象(具体内容)和组合对象(容器)的处理具有一致性,无需关系它们具体是哪种类型。

组合模式一般有两种对象:

  • 叶子对象:叶子对象是组合中最基本的对象,不具备子对象;

  • 组合对象:组合对象包含叶子对象和其他的组合对象。

2. 结构

组合模式结构包含以下几个角色:

  • 抽象层组件(Component):为叶子对象和组合对象声明接口,包含所有公共的行为的声明和实现,用于访问和管理组合对象的方法,如新增、删除、获取等,一般是抽象类或者接口;

  • 叶子节点(Leaf):实现抽象层组件,表示叶子对象,叶子节点没有子节点,是组合接口的最基本的单元,一般对于抽象层组件中的访问和管理组合对象的方法通过异常等方式进行处理;

  • 组合对象(Composite):实现抽象层组件,包含了子节点,可以是叶子节点也可以是组合对象,实现了抽象组件中定义的访问和管理对象的方法。

3. UML图

4. 实现

  • 抽象层组件

 abstract class Component {
     public abstract void add(Component component); 
     public abstract void remove(Component component); 
     public abstract Component get(int index); 
     public abstract void oper(); 
 }
  • 叶子节点

 class Leaf extends Component {
     public void add(Component component) {
         // 异常处理或者错误提示
     }
     public void remove(Component component) {
         // 异常处理或者错误提示
     } 
     public Component get(int index) {
         // 异常处理或者错误提示
     } 
     public void oper() {
         // 叶子节点业务方式实现
     }
 }
  • 组合对象

 class Composite extends Component {
     private List<Component> children = new ArrayList<>();
     
    public void add(Component component) {
         children.add(component);
     }
     public void remove(Component component) {
         children.remove(component);
     } 
     public Component get(int index) {
         return children.get(index);
     } 
     public void oper() {
         // 组合对象的业务方式实现
         // 循环调用子节点的业务方法
         for(Component com : children) {
             com.oper();
         }
     }
 }

5. 案例分析

本案例实现文章开头介绍的文件管理系统。

0️⃣ 实现一个抽象组件类

 // 抽象组件类
 public abstract class EntityFile {
     private String name;
 ​
     private int size;
 ​
     public String getName() {
         return this.name;
     }
 ​
     public int getSize() {
         return this.size;
     }
     // 管理文件的方法
     public abstract void add(EntityFile file);
 ​
     public abstract void remove(EntityFile file);
 ​
     public abstract void showChild();
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public void setSize(int size) {
         this.size = size;
     }
 }

1️⃣ 实现视频文件和音频文件

 public class MusicFile extends EntityFile {
 ​
     public MusicFile(String name, int size) {
         setName(name);
         setSize(size);
     }
 ​
     @Override
     public void add(EntityFile file) {
         throw new RuntimeException("当前文件不支持添加子文件");
     }
 ​
     @Override
     public void remove(EntityFile file) {
         throw new RuntimeException("当前文件没有子文件");
     }
 ​
     @Override
     public void showChild() {
         throw new RuntimeException("当前文件没有子文件");
     }
 }
 ​
 public class VideoFile extends EntityFile {
 ​
     public VideoFile(String name, int size) {
         setName(name);
         setSize(size);
     }
 ​
     @Override
     public void add(EntityFile file) {
         throw new RuntimeException("当前文件不支持添加子文件");
     }
 ​
     @Override
     public void remove(EntityFile file) {
         throw new RuntimeException("当前文件没有子文件");
     }
 ​
     @Override
     public void showChild() {
         throw new RuntimeException("当前文件没有子文件");
     }
 }

2️⃣ 实现文件夹的功能

public class Directory extends EntityFile {
 ​
     private List<EntityFile> children;
 ​
     public Directory(String name) {
         setName(name);
         children = new ArrayList<>();
         setSize(0);
     }
 ​
     @Override
     public void add(EntityFile file) {
         children.add(file);
         setSize();
     }
 ​
     @Override
     public void remove(EntityFile file) {
         children.remove(file);
         setSize();
     }
 ​
     @Override
     public void showChild() {
         System.out.println("文件夹名称为" + getName() + ", 共有" + children.size() + "个文件,大小为" + getSize());
         for(EntityFile file : children) {
             System.out.println(file.getName() + "-" + file.getSize());
         }
     }
 ​
     public void setSize() {
         setSize(children.stream().mapToInt(EntityFile::getSize).reduce(0, Integer::sum));
     }
 }

3️⃣ 客户端代码实现

 public class Client {
     public static void main(String[] args) {
         // 创建一个名为dir的文件夹
         EntityFile directory = new Directory("dir");
         // 创建两个音乐文件
         EntityFile daYu = new MusicFile("大鱼", 3452);
         EntityFile guaiKa = new MusicFile("怪咖", 5621);
         // 将文件放进到文件夹
         directory.add(daYu);
         directory.add(guaiKa);
         directory.showChild();
         // 创建视频文件
         EntityFile video = new VideoFile("视频1", 52153);
         directory.add(video);
         // 删除大鱼音乐文件
         directory.remove(daYu);
         directory.showChild();
     }
 }

6. 优缺点

✅️ 优点:

  • 统一接口:组合模式通过统一的接口,使客户端可以使用统一的方式处理单个对象和组合对象,简化了系统复杂性;

  • 可扩展:由于组合模式对象之间松耦合,新增叶子节点和组合对象时,都不需要对原有逻辑进行修改;

  • 灵活性: 组合模式可以为树形结构的面向对象提供灵活解决方案,处理更加见简单;

❌ 缺点:

  • 限制性:组合模式限制了某些操作的实现,在组合对象中添加特定类型的子对象需要进行类型检查,增加代码复杂度;

  • 叶子节点维护复杂:由于叶子节点继承自抽象组件,导致叶子节点必须实现抽象组件中定义的管理组件的方法,并且抛出异常,代码逻辑体现复杂;

7. 使用场景

  • 树形结构数据处理:当需要处理具有层次结构的数据,例如组织结构、文件系统、菜单等,可以使用组合模式来构建和操作这种树形结构;

  • 部分整体的关系:在具有部分和整体的层次结构中,可以使用统一的方式去处理它们之间的差异;

8. 总结

在本文中,我们首先介绍了组合模式的基本概念和结构,包括组件、叶子和组合对象,通过一个案例来深入了解了组合模式。总的来说,组合模式不仅是一种有用的设计模式,也是我们构建更加优秀软件系统的重要工具之一。

欢迎朋友们关注我的公众号📢📢📢:【码匠er】

我会持续更新关于技术的文章❤️🤎💚🧡💛

欢迎大家点赞👍 收藏 ⭐ 关注 💡三连支持一下~~~

查看文章过程中有问题或者有需要修改的地方,欢迎私聊我哦 🗨🗨🗨

不管世界变成什么样,我们都要加强自己自身能力~✊✊✊

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值