「全网最细 + 实战源码案例」设计模式——组合模式

简介: 组合模式(Composite Pattern)是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构。它允许客户端以一致的方式对待单个对象和对象集合,简化了复杂结构的处理。组合模式包含三个主要组件:抽象组件(Component)、叶子节点(Leaf)和组合节点(Composite)。通过这种模式,客户端可以统一处理简单元素和复杂元素,而无需关心其内部结构。适用于需要实现树状对象结构或希望以相同方式处理简单和复杂元素的场景。优点包括支持树形结构、透明性和遵循开闭原则;缺点是可能引入不必要的复杂性和过度抽象。

核心思想

  • 组合模式(Composite Pattern)是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式允许客户端以一致的方式对待单个对象和对象集合(容器),这样就可以用相同的方式对待单个对象(叶子节点)和由多个对象组成的集合(组合节点)。


结构

1. Component(抽象组件)

  • 定义了叶子节点和复合节点的共同接口,可以是抽象类或接口。

2. Leaf(叶子节点)

  • 在组合中表示单个对象,叶子节点没有子节点。

3. Composite(组合节点)

  • 表示有子节点对象,是树形结构的父节点,负责管理自己的子节点。


现实世界类比

大部分国家的军队都采用层次结构管理。 每支部队包括几个师, 师由旅构成, 旅由团构成, 团可以继续划分为排。 最后, 每个排由一小队实实在在的士兵组成。 军事命令由最高层下达, 通过每个层级传递, 直到每位士兵都知道自己应该服从的命令。


组合模式分类

透明组合模式(Transparent Composite Pattern)和安全组合模式(Safe Composite Pattern)都是组合模式的变种,组合模式是结构型设计模式的一种,主要用来将对象组合成树形结构来表示“部分-整体”的层次结构。它让客户端可以统一对待单个对象和组合对象。

  1. 透明组合模式(标准实现) :在透明组合模式中,所有的组件类(即叶子节点和容器节点)都具有统一的接口。叶子节点和容器节点对外暴露相同的行为,客户端无需区分它们是单个对象还是组合对象。因此,客户端可以直接对组合对象进行操作,就像操作单个对象一样。
    1. 优点:简化客户端代码,不需要显式地判断节点是叶子节点还是容器节点。
    2. 缺点:对于复杂的树形结构,可能会导致组件类暴露过多的方法,违反了单一职责原则。
  1. 安全组合模式: 安全组合模式则更加注重树形结构的安全性,它通过接口或抽象类对叶子节点和容器节点进行了区分,通常容器节点和叶子节点的接口是不同的。容器节点通常拥有管理子节点的方法,而叶子节点则没有。这就要求客户端必须判断节点是叶子节点还是容器节点,从而选择不同的操作。
    1. 优点:设计更清晰,叶子节点和容器节点分开处理,避免暴露不必要的方法,符合单一职责原则。
    2. 缺点:客户端需要区分叶子节点和容器节点,代码会更复杂。

简单总结:

  • 透明组合模式:叶子节点和容器节点对外有相同接口,客户端无需关心内部结构,代码简洁,但可能会暴露不必要的方法。
  • 安全组合模式:叶子节点和容器节点有不同接口,客户端需要判断节点类型,代码结构更清晰,但会略显复杂。

适用场景

  1. 需要实现树状对象结构:组合模式提供了两种共享公共接口的基本元素类型:简单叶子节点和组合节点(容器),容器中可以包含叶子节点和其他容器,以便构建树状嵌套递归对象结构。
  2. 希望客户端代码以相同方式处理简单和复杂元素:组合模式中定义的所有元素共用同一个接口,因此客户端不必在意所使用对象的具体类。

优缺点

优点:

  1. 树形结构:可以利用多态和递归机制方便地使用复杂树结构。
  2. 透明性:客户端统一地处理叶子节点和组合节点,无需关心其之间的区别。
  3. 遵循开闭原则:方便地添加新叶子节点和组合节点,且不影响现有代码。

缺点:

  1. 设计复杂:对于简单的结构,组合模式可能引入不必要的复杂性。
  2. 过度抽象:由于组合模式将所有节点都抽象为 Component 类,可能会导致一些不必要的抽象,影响代码的可读性。

实现步骤

  1. 确保应用的核心模型能够以树状结构表示。 尝试将其分解为简单元素和容器。 记住, 容器必须能够同时包含简单元素和其他容器。
  2. 声明组件接口及其一系列方法, 这些方法对简单和复杂元素都有意义。
  3. 创建一个叶节点类表示简单元素。 程序中可以有多个不同的叶节点类。
  4. 创建一个容器类表示复杂元素。 在该类中, 创建一个数组成员变量来存储对于其子元素的引用。 该数组必须能够同时保存叶节点和容器, 因此请确保将其声明为组合接口类型。

实现组件接口方法时, 记住容器应该将大部分工作交给其子元素来完成。

  1. 最后, 在容器中定义添加和删除子元素的方法。

记住, 这些操作可在组件接口中声明。 这将会违反接口隔离原则, 因为叶节点类中的这些方法为空。 但是, 这可以让客户端无差别地访问所有元素, 即使是组成树状结构的元素。


示例

// 抽象组件——菜单组件
public abstract class MenuComponent {

    protected String name;
    protected int level;

    public void add(MenuComponent menuComponent){
        throw new UnsupportedOperationException("不支持添加操作");
    }

    public void remove(MenuComponent menuComponent){
        throw new UnsupportedOperationException("不支持删除操作");
    }

    public MenuComponent getChild(int index){
        throw new UnsupportedOperationException("不支持获取子菜单操作");
    }

    public String getName(){
        return name;
    }

    public abstract void print();
}

// 叶子节点——菜单项
public class MenuItem extends MenuComponent{

    public MenuItem(String name, int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void print() {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println("菜单项:" + name);
    }
}

// 复合节点——菜单
public class Menu extends MenuComponent{

    private List<MenuComponent> menuComponentList = new ArrayList<>();

    public Menu(String name, int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void add(MenuComponent menuComponent) {
        menuComponentList.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
        menuComponentList.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int index) {
        return menuComponentList.get(index);
    }

    @Override
    public void print() {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println("菜单:" + name);
        for (MenuComponent menuComponent : menuComponentList) {
            menuComponent.print();
        }
    }
}

public class Client {
    public static void main(String[] args) {

        MenuComponent menu = new Menu("系统管理", 1);

        MenuComponent menu1 = new Menu("菜单管理", 2);
        menu1.add(new MenuItem("页面访问", 3));
        menu1.add(new MenuItem("展开菜单", 3));
        menu1.add(new MenuItem("编辑菜单", 3));
        menu1.add(new MenuItem("删除菜单", 3));
        menu1.add(new MenuItem("新增菜单", 3));

        MenuComponent menu2 = new Menu("权限配置", 2);
        menu2.add(new MenuItem("页面访问", 3));
        menu2.add(new MenuItem("提交保存", 3));

        MenuComponent menu3 = new Menu("角色管理", 2);
        menu3.add(new MenuItem("页面访问", 3));
        menu3.add(new MenuItem("新增角色", 3));
        menu3.add(new MenuItem("修改角色", 3));

        menu.add(menu1);
        menu.add(menu2);
        menu.add(menu3);

        menu.print();
    }
}
AI 代码解读

与其他模式的关系

  • 桥接模式状态模式策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。
  • 你可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式运行。
  • 责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。
  • 你可以使用迭代器模式来遍历组合树。
  • 你可以使用访问者模式对整个组合树执行操作。
  • 你可以使用享元模式实现组合树的共享叶节点以节省内存。
  • 组合装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。
  • 装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。
  • 大量使用组合装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。
目录
打赏
0
19
22
4
176
分享
相关文章
Python 高级编程与实战:深入理解设计模式与软件架构
本文深入探讨了Python中的设计模式与软件架构,涵盖单例、工厂、观察者模式及MVC、微服务架构,并通过实战项目如插件系统和Web应用帮助读者掌握这些技术。文章提供了代码示例,便于理解和实践。最后推荐了进一步学习的资源,助力提升Python编程技能。
「全网最细 + 实战源码案例」设计模式——命令模式
命令模式(Command Pattern)是一种行为型设计模式,将请求封装成独立对象,从而解耦请求方与接收方。其核心结构包括:Command(命令接口)、ConcreteCommand(具体命令)、Receiver(接收者)和Invoker(调用者)。通过这种方式,命令的执行、撤销、排队等操作更易扩展和灵活。 适用场景: 1. 参数化对象以操作。 2. 操作放入队列或远程执行。 3. 实现回滚功能。 4. 解耦调用者与接收者。 优点: - 遵循单一职责和开闭原则。 - 支持命令组合和延迟执行。 - 可实现撤销、恢复功能。 缺点: - 增加复杂性和类数量。
64 14
「全网最细 + 实战源码案例」设计模式——命令模式
「全网最细 + 实战源码案例」设计模式——责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,允许将请求沿着处理者链进行发送。每个处理者可以处理请求或将其传递给下一个处理者,从而实现解耦和灵活性。其结构包括抽象处理者(Handler)、具体处理者(ConcreteHandler)和客户端(Client)。适用于不同方式处理不同种类请求、按顺序执行多个处理者、以及运行时改变处理者及其顺序的场景。典型应用包括日志处理、Java Web过滤器、权限认证等。
61 13
「全网最细 + 实战源码案例」设计模式——责任链模式
「全网最细 + 实战源码案例」设计模式——策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,用于定义一系列可替换的算法或行为,并将它们封装成独立的类。通过上下文持有策略对象,在运行时动态切换算法,提高代码的可维护性和扩展性。适用于需要动态切换算法、避免条件语句、经常扩展算法或保持算法独立性的场景。优点包括符合开闭原则、运行时切换算法、解耦上下文与策略实现、减少条件判断;缺点是增加类数量和策略切换成本。示例中通过定义抽象策略接口和具体策略类,结合上下文类实现动态算法选择。
68 8
「全网最细 + 实战源码案例」设计模式——策略模式
「全网最细 + 实战源码案例」设计模式——模板方法模式
模板方法模式是一种行为型设计模式,定义了算法的骨架并在父类中实现不变部分,将可变部分延迟到子类实现。通过这种方式,它避免了代码重复,提高了复用性和扩展性。具体步骤由抽象类定义,子类实现特定逻辑。适用于框架设计、工作流和相似算法结构的场景。优点包括代码复用和符合开闭原则,缺点是可能违反里氏替换原则且灵活性较低。
68 7
「全网最细 + 实战源码案例」设计模式——模板方法模式
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
169 11
设计模式:工厂方法模式(Factory Method)
工厂方法模式是一种创建型设计模式,通过将对象的创建延迟到子类实现解耦。其核心是抽象工厂声明工厂方法返回抽象产品,具体工厂重写该方法返回具体产品实例。适用于动态扩展产品类型、复杂创建逻辑和框架设计等场景,如日志记录器、数据库连接池等。优点包括符合开闭原则、解耦客户端与具体产品;缺点是可能增加类数量和复杂度。典型应用如Java集合框架、Spring BeanFactory等。
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
「全网最细 + 实战源码案例」设计模式——模式扩展(配置工厂)
该设计通过配置文件和反射机制动态选择具体工厂,减少硬编码依赖,提升系统灵活性和扩展性。配置文件解耦、反射创建对象,新增产品族无需修改客户端代码。示例中,`CoffeeFactory`类加载配置文件并使用反射生成咖啡对象,客户端调用时只需指定名称即可获取对应产品实例。
92 40
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。