设计模式-组合模式

写在前面

Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!

第一话:初代营销系统

技术背景

营销系统初期,技术负责人小易考虑到初期仅需支持两种渠道,采用直接耦合方式可快速上线

邮件活动
typescript 复制代码
@Getter
@Setter
public class EmailCampaign {

    private String name;

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

    public void execute() {
        System.out.println("执行电子邮件活动: " + name);
    }

}
社交媒体活动
typescript 复制代码
@Getter
@Setter
public class SocialMediaCampaign {

    private String name;

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

    public void execute() {
        System.out.println("执行社交媒体活动: " + name);
    }

}
组合活动
csharp 复制代码
public class BundleCampaign {

    private EmailCampaign emailCampaign;

    private SocialMediaCampaign socialMediaCampaign;

    public BundleCampaign(EmailCampaign emailCampaign,SocialMediaCampaign socialMediaCampaign){
        this.emailCampaign = emailCampaign;
        this.socialMediaCampaign = socialMediaCampaign;
    }

    public void execute(){
        emailCampaign.execute();
        socialMediaCampaign.execute();

        System.out.println("执行一系列活动: " + emailCampaign.getName() + " + " + socialMediaCampaign.getName());
    }

}
架构示意图
css 复制代码
A[营销系统] 
    A --> B[邮件活动]
    A --> C[社交活动]
    A --> D[硬编码组合]

埋下隐患

  • 新增活动类型需修改BundleCampaign类
  • 组合深度限制为固定两层

第二话:需求突袭

技术背景市场总监提出P0级需求:支持三级组合营销(主活动=短信+子组合(邮件+社交)),要求不影响线上运行。

临时方案:采用硬编码支持新类型,并新建一个短信活动。

短信活动
typescript 复制代码
@Getter
@Setter
public class SMSCampaign {

    private String name;

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

    public void execute() {
        System.out.println("执行短信活动: " + name);
    }

}
硬编码组合类
typescript 复制代码
public class BundleCampaign {

    List<Object> campaigns = new ArrayList<>();

    public void addCampaign(Object campaign) {
        campaigns.add(campaign);
    }

    public void execute() {
        for (Object obj : campaigns) {
            if (obj instanceof EmailCampaign) {
                ((EmailCampaign) obj).execute();
            }
            else if (obj instanceof SocialMediaCampaign) {
                ((SocialMediaCampaign) obj).execute();
            }
            else if (obj instanceof SMSCampaign) {
                ((SMSCampaign) obj).execute();
            }
            else {
                return;
            }

        }
    }
}

方案缺陷

  • 当需要新增第四种活动时,不得不在 BundleCampaign 中添加更多 if-else
  • 每次新增活动,都需要修改核心逻辑,严重影响到测试的完整性
  • 判断中需要类型强制转换,嵌套组合支持不完善

第三话:设计模式重构

技术背景 : 技术负责人小易考虑到若不对当前营销系统进行重构,很有可能在未来的某天出现重大系统故障问题,决定使用组合模式进行重构。

设计模式解析

核心思想 :将对象组织成树形结构,统一处理单个对象和组合对象。
主要角色

  1. Component (抽象类):定义统一接口(如 execute()
  2. Leaf(叶子节点):基础活动(邮件、社交)
  3. Composite (组合节点):可包含其他 Component 的组合活动(BundleCampaign)
重构:应用组合模式
1. 定义抽象组件
typescript 复制代码
@Getter
@Setter
public abstract class Campaign {

    protected String name;

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

    /**
     * 定义活动执行抽象方法
     */
    public abstract void execute();

    public void add(Campaign campaign) {
        throw new UnsupportedOperationException("不支持组合活动");
    }

}
2. 叶子节点:基础活动
java 复制代码
public class EmailCampaign extends Campaign {

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

    @Override
    public void execute() {
        System.out.println("执行电子邮件活动: " + name);
    }

}

public class SMSCampaign extends Campaign {

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

    @Override
    public void execute() {
        System.out.println("执行短信活动: " + name);
    }
}

public class SocialMediaCampaign extends Campaign {

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

    @Override
    public void execute() {
        System.out.println("执行社交媒体活动: " + name);
    }
}
3. 组合节点:可嵌套的组合活动
typescript 复制代码
public class BundleCampaign extends Campaign {

    private List<Campaign> campaigns = new ArrayList<>();

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

    @Override
    public void execute() {
        System.out.println(name + " 开启!");

        for (Campaign c : campaigns) {
            c.execute();
        }
    }

    @Override
    public void add(Campaign campaign) {
        campaigns.add(campaign);
    }

}
测试代码
ini 复制代码
public class CompositeTest {
    @Test
    public void testCampaign() {
        Campaign emailCampaign = new EmailCampaign("双十一折扣");
        Campaign socialMediaCampaign = new SocialMediaCampaign("微博热搜");

        Campaign bundleCampaign = new BundleCampaign("主活动");
        bundleCampaign.add(emailCampaign);
        bundleCampaign.add(socialMediaCampaign);

        Campaign childBundle = new BundleCampaign("子活动");
        childBundle.add(new SMSCampaign("追加优惠"));
        bundleCampaign.add(childBundle);

        bundleCampaign.execute();
    }
}

输出

makefile 复制代码
主活动开启!
执行电子邮件活动: 双十一折扣
执行社交媒体活动: 微博热搜
子活动开启!
执行短信活动: 追加优惠
重构后效果
系统改进点
  1. 新增类型无需修改已有代码(符合开闭原则)
  2. 客户端无需区分叶子/组合节点(统一接口)
  3. 支持无限层级嵌套(灵活组合)
复杂度对比
维度 重构前 重构后
新增活动类型 修改核心类 新增叶子类即可
组合嵌套深度 固定2层 无限递归
类型安全检查 运行时判断 编译时保障
扩展成本 O(n)线性增长 O(1)恒定

长话短说

核心思想与使用指南

核心思想:
  • 统一性:客户端无需区分叶子节点和组合节点
  • 递归组合:通过树形结构实现无限嵌套
实施路线图
  1. 识别树形结构:找出系统中的层次关系
  2. 抽象组件接口:定义通用操作方法
  3. 区分叶子节点:实现不可再分的元素
  4. 构建复合节点:实现可嵌套的容器
  5. 实现递归逻辑:在复合节点中遍历子元素
  6. 添加类型约束(可选):通过安全模式限制操作
代码步骤
  1. 定义抽象类/接口,声明通用方法(如 execute())。
  2. 创建叶子类,实现基础功能。
  3. 创建组合类,管理子组件集合,并实现递归调用。
  4. 客户端通过抽象类型操作所有对象。

最佳实践场景

  1. 文件系统:文件与文件夹的统一管理
  2. 组织架构:部门与员工的层级关系
  3. UI组件:窗口与控件的嵌套组合
  4. 语法树:程序结构的抽象表示

关键决策点

何时选择组合模式?
  • 需要表示对象的部分-整体层次结构
  • 希望客户端忽略组合与单个对象的不同
  • 需要递归组合的结构化数据
何时避免使用?
  • 系统不存在明显的层次结构
  • 组合操作需要完全不同的接口
  • 性能敏感的叶子节点操作
相关推荐
tan180°28 分钟前
版本控制器Git(1)
c++·git·后端
GoGeekBaird29 分钟前
69天探索操作系统-第50天:虚拟内存管理系统
后端·操作系统
_丿丨丨_33 分钟前
Django下防御Race Condition
网络·后端·python·django
JohnYan1 小时前
工作笔记 - btop安装和使用
后端·操作系统
我愿山河人间1 小时前
Dockerfile 和 Docker Compose:容器化世界的两大神器
后端
掘金码甲哥1 小时前
golang倒腾一款简配的具有请求排队功能的并发受限服务器
后端
重庆穿山甲1 小时前
装饰器模式实战指南:动态增强Java对象的能力
后端
卑微小文1 小时前
企业级IP代理安全防护:数据泄露风险的5个关键防御点
前端·后端·算法
lovebugs1 小时前
如何保证Redis与MySQL双写一致性?分布式场景下的终极解决方案
后端·面试
斑鸠喳喳1 小时前
模块系统 JPMS
java·后端