写在前面
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组件:窗口与控件的嵌套组合
- 语法树:程序结构的抽象表示
关键决策点
何时选择组合模式?
- 需要表示对象的部分-整体层次结构
- 希望客户端忽略组合与单个对象的不同
- 需要递归组合的结构化数据
何时避免使用?
- 系统不存在明显的层次结构
- 组合操作需要完全不同的接口
- 性能敏感的叶子节点操作