设计模式——组合设计模式(结构型)

article/2025/8/10 4:47:11

摘要

组合设计模式是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构,使客户端对单个对象和组合对象具有一致的访问方式。它包含抽象组件、叶子节点和组合节点,具有统一处理、支持递归结构和易扩展等优点,适用树形结构场景,如组织架构、菜单、规则集等。

1. 组合设计模式定义

组合设计模式(Composite Pattern)是一种结构型设计模式,它用于将对象组合成树形结构以表示“部分-整体”的层次结构,使得客户端对单个对象和组合对象具有一致的访问方式。组合模式允许你将对象组合成树形结构来表示“整体/部分”层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性

1.1.1. ✅ UML 结构图(简化):

         Component(抽象组件)↑┌──────┴──────┐
Leaf(叶子节点)   Composite(容器/组合节点)
  • Component:定义组合中对象的接口,可以是抽象类或接口。
  • Leaf:叶子节点,表示树结构中的子节点,不能再包含其他子对象。
  • Composite:组合节点,表示可以拥有子节点的对象,它实现了 Component 接口,并维护子组件集合。

1.1.2. ✅ 举例说明(风控场景类比):

比如在风控系统中,有一套规则系统:

  • 单一规则(如“手机号黑名单检查”)是 Leaf
  • 组合规则(如“黑名单检查 + 地域异常组合规则”)是 Composite
  • 客户端调用统一的 evaluate() 方法即可,不需要关心规则是单一还是组合。

1.1.3. ✅ 核心优势:

优点

说明

统一处理单个和组合对象

客户端代码一致,不用区别对待

支持递归结构

适用于树形结构,如组织架构、菜单、规则集

易于扩展

增加新的 LeafComposite 不影响原有代码

2. 组合设计模式结构

2.1. 组合设计模式类图

结构说明

  1. 组件 (Component) 接口描述了树中简单项目和复杂项目所共有的操作。
  2. 叶节点 (Leaf) 是树的基本结构, 它不包含子项目。一般情况下, 叶节点最终会完成大部分的实际工作, 因为它们无法将工作指派给其他部分。
  3. 容器 (Container)——又名 “组合 (Composite)”——是包含叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的具体类, 它只通过通用的组件接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。
  4. 客户端 (Client) 通过组件接口与所有项目交互。 因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。

2.2. 组合模式时序图

3. 组合设计模式实现方式

将单个对象(叶子节点)和组合对象(容器节点)统一抽象为一个组件接口,客户端无需关心操作对象是单一元素还是组合结构,均通过统一接口访问。

3.1. Step 1:定义统一接口 Component

public interface Component {void operation();
}

3.2. Step 2:实现叶子节点 Leaf

public class Leaf implements Component {private String name;public Leaf(String name) {this.name = name;}@Overridepublic void operation() {System.out.println("Leaf [" + name + "] 执行操作");}
}

3.3. Step 3:实现组合节点 Composite

import java.util.ArrayList;
import java.util.List;public class Composite implements Component {private String name;private List<Component> children = new ArrayList<>();public Composite(String name) {this.name = name;}public void add(Component component) {children.add(component);}public void remove(Component component) {children.remove(component);}@Overridepublic void operation() {System.out.println("Composite [" + name + "] 开始操作");for (Component child : children) {child.operation();}}
}

3.4. Step 4:客户端使用(Client)

public class Client {public static void main(String[] args) {Leaf leaf1 = new Leaf("规则 A");Leaf leaf2 = new Leaf("规则 B");Composite composite1 = new Composite("组合规则1");composite1.add(leaf1);composite1.add(leaf2);Leaf leaf3 = new Leaf("规则 C");Composite root = new Composite("根规则");root.add(composite1);root.add(leaf3);root.operation(); // 统一调用}
}

4. 组合设计模式适合场景

组合设计模式(Composite Pattern)适用于 “树形结构” 的场景,它能让客户端以统一方式处理单个对象和组合对象。以下是其适合与不适合使用的场景总结:

4.1. ✅ 适合使用组合设计模式的场景

场景

说明

具有树形结构的业务模型

如文件系统、组织架构、菜单栏、风控规则树等。

需要统一处理单个对象和组合对象

客户端希望一致地使用所有元素(比如 evaluate()operation()),而不关心它们是叶子还是组合。

组合对象和单个对象行为一致

当组合对象与叶子节点有相同的行为逻辑(如日志打印、状态传递等)。

需要支持递归组合对象

组合中可以包含其他组合(嵌套结构),比如组合规则中嵌套组合规则。

客户端不应依赖具体实现结构

只与 Component 接口交互,提高可扩展性与解耦能力。

4.2. ❌ 不适合使用组合设计模式的场景

场景

原因

对象之间结构关系简单

如果业务不涉及树形结构,引入组合反而增加系统复杂度。

叶子节点与组合节点的行为差异很大

若行为差异明显(如操作参数、逻辑完全不同),统一接口会导致实现臃肿。

对性能要求极高的系统

递归遍历树结构可能导致性能瓶颈,不如精细控制流程。

对象的生命周期和依赖复杂

比如依赖注入、事务、缓存等特性在嵌套结构中处理较困难。

频繁变动的数据结构

若组合结构经常调整、扩展,维护成本会变高,灵活性不如策略模式或责任链模式。

4.3. 📝 实战建议(风控项目中的应用)

项目结构

是否适合组合模式

风控规则树(嵌套组合规则 + 原子规则)

✅ 适合

单一条件判断规则(如手机号是否存在)

❌ 不适合(可用策略/责任链)

配置驱动型规则执行器

✅ 适合(组合模式 + Spring 注入)

强依赖流程顺序的规则链

❌ 不适合(更适合责任链模式)

5. 组合设计模式实战示例

组合设计模式实战示例 — 风控规则引擎

5.1. 统一接口 Component

public interface RuleComponent {boolean evaluate(RiskContext context);
}
  • evaluate 方法表示对风控上下文做规则判断,返回是否通过。

5.2. 叶子节点实现(单一规则)

import org.springframework.stereotype.Component;
import javax.annotation.Resource;@Component("blacklistRule")
public class BlacklistRule implements RuleComponent {@Resource(name = "blacklistService")private BlacklistService blacklistService; // 依赖注入,模拟黑名单校验服务@Overridepublic boolean evaluate(RiskContext context) {System.out.println("执行黑名单规则");return !blacklistService.isBlacklisted(context.getUserId());}
}

5.3. 组合节点实现(组合规则)

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;@Component("compositeRule")
public class CompositeRule implements RuleComponent {@Autowiredprivate List<RuleComponent> children;  // 注入所有 RuleComponent 实现类(包括叶子和组合)@Overridepublic boolean evaluate(RiskContext context) {System.out.println("执行组合规则,包含 " + children.size() + " 条子规则");for (RuleComponent rule : children) {if (!rule.evaluate(context)) {return false;  // 只要有一条规则不通过,整个组合规则失败}}return true;}
}

注意: 这里 children 注入的是所有 RuleComponent 实现,实际场景中可能用 qualifier 或配置区分组合里的具体规则。

5.4. 风控上下文类

public class RiskContext {private String userId;private double loanAmount;// 其他业务相关参数// 省略构造、getter、setterpublic RiskContext(String userId, double loanAmount) {this.userId = userId;this.loanAmount = loanAmount;}public String getUserId() { return userId; }public double getLoanAmount() { return loanAmount; }
}

5.5. 依赖服务示例(模拟黑名单服务)

import org.springframework.stereotype.Service;@Service("blacklistService")
public class BlacklistService {public boolean isBlacklisted(String userId) {// 模拟黑名单查询,假设 userId 为 "1001" 是黑名单return "1001".equals(userId);}
}

5.6. 客户端调用(风控引擎)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class RiskEngine {@Autowiredprivate RuleComponent compositeRule;  // 注入组合规则,入口public boolean evaluate(RiskContext context) {return compositeRule.evaluate(context);}
}

5.7. SpringBoot 启动类及测试

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Autowired;@SpringBootApplication
public class RiskApp implements CommandLineRunner {@Autowiredprivate RiskEngine riskEngine;public static void main(String[] args) {SpringApplication.run(RiskApp.class, args);}@Overridepublic void run(String... args) {RiskContext context1 = new RiskContext("1000", 5000);System.out.println("用户1000风控结果: " + riskEngine.evaluate(context1));RiskContext context2 = new RiskContext("1001", 5000);System.out.println("用户1001风控结果: " + riskEngine.evaluate(context2));}
}

总结

  • 统一接口 RuleComponent,定义规则通用方法。
  • 叶子节点实现单个规则(如黑名单),组合节点实现多个规则的组合逻辑。
  • 通过 @Autowired 注入,Spring 自动管理组件实例。
  • 业务调用时直接使用组合规则,自动递归调用子规则。
  • 采用字段注入方式,符合你“不用构造函数注入”的要求。

6. 组合设计模式思考

博文参考

  • 开放平台
  • 组合设计模式

http://www.hkcw.cn/article/ljqgxVQxOk.shtml

相关文章

Launcher3体系化之路

&#x1f44b; 欢迎来到Launcher 3 背景 车企对于桌面的排版布局好像没有手机那般复杂&#xff0c;但也有一定的需求。部分场景下&#xff0c;要考虑的上下文比手机要多一些&#xff0c;比如有如下的一些场景&#xff1a; 手车互联。HiCar&#xff0c;CarPlay&#xff0c;An…

pikachu通关教程- over permission

如果使用A用户的权限去操作B用户的数据&#xff0c;A的权限小于B的权限&#xff0c;如果能够成功操作&#xff0c;则称之为越权操作。 越权漏洞形成的原因是后台使用了 不合理的权限校验规则导致的。 水平越权 当我们以Lucy账号登录&#xff0c;查询个人信息时&#xff0c;会有…

刷leetcode hot100--矩阵6/1

1.螺旋矩阵【很久】6/1【感觉就是思路的搬运工&#xff0c;没完全理解】 54. 螺旋矩阵 - 力扣&#xff08;LeetCode&#xff09; 原来想 但是如果是奇数矩阵&#xff0c;遍历不到中间 解决思路&#xff1a; 用left,right,top,down标记/限定每次遍历的元素&#xff0c;每次从…

Redis最佳实践——秒杀系统设计详解

基于Redis的高并发秒杀系统设计&#xff08;十万级QPS&#xff09; 一、秒杀系统核心挑战 瞬时流量洪峰&#xff1a;100万 QPS请求冲击库存超卖风险&#xff1a;精准扣减防止超卖系统高可用性&#xff1a;99.99%服务可用性要求数据强一致性&#xff1a;库存/订单/支付状态同步…

搭建基于VsCode的ESP32的开发环境教程

一、VsCode搜索ESP-IDF插件 根据插件处搜索找到ESP-IDF并安装 安装完成 二、配置安装ESP-IDF 配置IDF 按照如下配置&#xff0c;点击安装 安装完成 三、使用案例程序 创建一个闪光灯的例子程序&#xff0c;演示程序编译下载。 选择blink例子&#xff0c;闪烁LED的程序 选…

实现RabbitMQ多节点集群搭建

目录 引言 一、环境准备 二、利用虚拟机搭建 ​ 三、镜像集群配置 四、HAProxy实现负载均衡(主用虚拟机操作) 五、测试RabbitMQ集群搭建情况 引言 在现代分布式系统中&#xff0c;消息队列&#xff08;Message Queue&#xff09;扮演着至关重要的角色,而 RabbitMQ 作为…

B站视频下载器 v1.0.4|免登录下载1080P视频

核心亮点 ✅ 无需登录下载1080P高清视频✅ 支持Windows/macOS双平台✅ 纯净无广告完全免费✅ 可单独下载视频/音频/弹幕/字幕/封面 三步极简操作 粘贴B站视频链接选择保存位置点击「开始下载」 特色功能 独立下载选项&#xff08;视频/音频/弹幕/字幕/封面&#xff09;登录…

LLM-MPC混合架构:车载大语言模型用来增强自动驾驶系统

1. 概述 2025年&#xff0c;苏黎世研究团队在RSS2025会议上正式提出「LLM-MPC混合架构」&#xff0c;标志着大语言模型&#xff08;LLM&#xff09;在自动驾驶系统中的实用化迈出关键一步。该方案旨在解决传统深度学习模型在极端交通场景中泛化能力不足的问题。通过在车载终端…

leetcode-hot-100 (矩阵)

1、矩阵置零 题目链接&#xff1a;矩阵置零 题目描述&#xff1a;给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 解答 方法一&#xff1a;使用一个二维数组 这是我看到这道题目的第一个想法&am…

黑马Java面试笔记之Redis篇(分布式锁)

面试题 我看你做的项目中&#xff0c;都用到了redis&#xff0c;你在最近的项目中那些场景使用了redis呢 如果回答了分布式锁&#xff0c;那么就会有以下这个问题 redis分布式锁&#xff0c;是如何实现的&#xff1f; 需要结合项目中的业务进行回答&#xff0c;通常情况下&…

建筑兔零基础python自学记录102|Beautiful Soup库(1)-15

1、安装Beautiful Soup 2、使用测试网页获取源代码 This is a python demo page a)法1&#xff1a;直接网页右键获取 b)法2&#xff1a;request库 import requests rrequests.get(https://python123.io/ws/demo.html) print(r.text) 3、使用Beautiful Soup库 同样解析出了源…

【Java学习笔记】枚举

枚举(enum) 一、基本介绍 引出关键字&#xff1a;enum&#xff0c;全称为enumerate 枚举是一组常量集合 理解&#xff1a;枚举属于一种特殊的类&#xff0c;里面只包含一组有限的特定的对象 二、使用场景 当一个类的属性有限定条件时&#xff08;例如一个星期只能有七天&…

FFmpeg学习笔记

1. 播放器的架构 2. 播放器的渲染流程 3. ffmpeg下载与安装 3.0 查看PC是否已经安装了ffmpeg ffmpeg 3.1 下载 wget https://ffmpeg.org/releases/ffmpeg-7.0.tar.gz 3.2 解压 tar zxvf ffmpeg-7.0.tar.gz && cd ./ffmpeg-7.0 3.3 查看配置文件 ./configure …

buuctf-web

[极客大挑战 2019]Havefun 控制台检查代码修改url [极客大挑战 2019]EasySQL 密码输入 出现 sql输入错误 判断为 单引号注入 [ACTF2020 新生赛]Include 点击提示出现?file说明是文件包含问题 构造url filephp://filter/readconvert.base64-encode/resourceflag.php php:…

SAR ADC 异步逻辑设计

SAR ADC的逻辑是重要的一个模块&#xff0c;可以分为同步逻辑和异步逻辑&#xff0c;对于低速SAR ADC&#xff0c;一般采用同步逻辑&#xff0c;对于高速SAR ADC&#xff0c;一般采用异步逻辑。 这里讲一下异步逻辑的设计&#xff0c;异步逻辑一般不需要外部时钟&#xff08;当…

广告拦截器:全方位拦截,畅享无广告体验

在数字时代&#xff0c;广告无处不在。无论是浏览网页、使用社交媒体&#xff0c;还是观看视频&#xff0c;广告的频繁弹出常常打断我们的体验&#xff0c;让人不胜其烦。更令人担忧的是&#xff0c;一些广告可能包含恶意软件&#xff0c;威胁我们的设备安全和个人隐私。AdGuar…

MMRL: Multi-Modal Representation Learning for Vision-Language Models(多模态表示学习)

摘要 预训练的VLMs,对于跨任务的迁移学习至关重要&#xff0c;然而&#xff0c;在few-shot数据集上微调会导致过拟合&#xff0c;降低在新任务上的性能。为解决这个问题&#xff0c;提出一种新的多模态表征学习框架&#xff08;MMRL&#xff09;,该框架引入了一个共享、可学习…

【芯片学习】555

一、引脚作用 二、原理图 三、等效原理图 1.比较器 同相输入端大于反相输入端&#xff0c;输出高电平&#xff0c;反之亦然 2.三极管 给它输入高电平就可以导通 3.模拟电路部分 4.数字电路部分 这部分的核心是RS触发器&#xff0c;R-reset代表0&#xff0c;set是置位代表1&am…

从线性代数到线性回归——机器学习视角

真正不懂数学就能理解机器学习其实是个神话。我认为&#xff0c;AI 在商业世界可以不懂数学甚至不懂编程也能应用&#xff0c;但对于技术人员来说&#xff0c;一些基础数学是必须的。本文收集了我认为理解学习本质所必需的数学基础&#xff0c;至少在概念层面要掌握。毕竟&…

DAY 36 超大力王爱学Python

仔细回顾一下神经网络到目前的内容&#xff0c;没跟上进度的同学补一下进度。 作业&#xff1a;对之前的信贷项目&#xff0c;利用神经网络训练下&#xff0c;尝试用到目前的知识点让代码更加规范和美观。探索性作业&#xff08;随意完成&#xff09;&#xff1a;尝试进入nn.Mo…