写在前面
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
- 每次新增活动,都需要修改核心逻辑,严重影响到测试的完整性
- 判断中需要类型强制转换,嵌套组合支持不完善
第三话:设计模式重构
技术背景 : 技术负责人小易考虑到若不对当前营销系统进行重构,很有可能在未来的某天出现重大系统故障问题,决定使用组合模式进行重构。
设计模式解析
核心思想 :将对象组织成树形结构,统一处理单个对象和组合对象。
主要角色:
- Component (抽象类):定义统一接口(如 execute())
- Leaf(叶子节点):基础活动(邮件、社交)
- 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
              
              
            
          
          主活动开启!
执行电子邮件活动: 双十一折扣
执行社交媒体活动: 微博热搜
子活动开启!
执行短信活动: 追加优惠重构后效果
系统改进点
- 新增类型无需修改已有代码(符合开闭原则)
- 客户端无需区分叶子/组合节点(统一接口)
- 支持无限层级嵌套(灵活组合)
复杂度对比
| 维度 | 重构前 | 重构后 | 
|---|---|---|
| 新增活动类型 | 修改核心类 | 新增叶子类即可 | 
| 组合嵌套深度 | 固定2层 | 无限递归 | 
| 类型安全检查 | 运行时判断 | 编译时保障 | 
| 扩展成本 | O(n)线性增长 | O(1)恒定 | 
长话短说
核心思想与使用指南
核心思想:
- 统一性:客户端无需区分叶子节点和组合节点
- 递归组合:通过树形结构实现无限嵌套
实施路线图
- 识别树形结构:找出系统中的层次关系
- 抽象组件接口:定义通用操作方法
- 区分叶子节点:实现不可再分的元素
- 构建复合节点:实现可嵌套的容器
- 实现递归逻辑:在复合节点中遍历子元素
- 添加类型约束(可选):通过安全模式限制操作
代码步骤
- 定义抽象类/接口,声明通用方法(如 execute())。
- 创建叶子类,实现基础功能。
- 创建组合类,管理子组件集合,并实现递归调用。
- 客户端通过抽象类型操作所有对象。
最佳实践场景
- 文件系统:文件与文件夹的统一管理
- 组织架构:部门与员工的层级关系
- UI组件:窗口与控件的嵌套组合
- 语法树:程序结构的抽象表示
关键决策点
何时选择组合模式?
- 需要表示对象的部分-整体层次结构
- 希望客户端忽略组合与单个对象的不同
- 需要递归组合的结构化数据
何时避免使用?
- 系统不存在明显的层次结构
- 组合操作需要完全不同的接口
- 性能敏感的叶子节点操作