先抛一个反直觉的结论:策略模式最重要的价值不是帮你少写几个 if-else,而是把“变化”和“稳定”在代码里分开,让变化的部分可以在运行期被替换,同时让稳定的部分稳如磐石。
这些年看过的项目里,真正把策略模式用好的,代码读起来像一份清晰的说明书;用不好的,就是给一个本来很简单的问题套了一层又一层抽象,最后维护的人想骂人。所以这篇文章我不想只贴一段策略模式的教科书代码,而是想聊清楚:策略模式到底在解决什么问题、它的边界在哪里、为什么很多场景下你其实不需要它,以及它和工厂、状态模式这些“亲戚”到底怎么区分。最后会结合最近的子代理(subagent)设计范式,聊聊策略模式在新场景里是不是过时了。
这个内容适合几类人:正在学设计模式的学生,尤其是对着期末作业或大作业不知道怎么落地的;写了两三年业务代码、发现自己的 if-else 越堆越长但不敢乱动的开发者;以及那些已经在用策略模式,但偶尔会犹豫“这里到底该不该用策略”的工程师。我会尽量用大白话把原理讲透,代码部分会给出 Java 和 C++ 两版,方便不同技术栈的读者对照。
1. 策略模式真正要解决的问题:不是代码重复,是变化点的失控
很多人对策略模式的第一印象是“消除 if-else”。这个说法不算错,但特别容易误导人。因为 if-else 本身不是坏东西,在分支很少、逻辑很稳定的情况下,if-else 反而是最直观、最好读的写法。真正让代码变烂的,是那些“不确定未来还会加多少分支”的 if-else,以及多个地方重复出现同一组分支判断。
1.1 从一段真实的促销报价代码说起
我举个例子,假设你在做一个电商系统的报价模块,最初的需求很简单:普通用户原价购买,VIP 用户打 9 折,企业用户打 8 折。很多人的第一版代码可能长这样:
java复制public double calculatePrice(String userType, double originalPrice) {
if ("vip".equals(userType)) {
return originalPrice * 0.9;
} else if ("enterprise".equals(userType)) {
return originalPrice * 0.8;
} else {
return originalPrice;
}
}
这段代码有问题吗?如果需求永远只有这样三种用户,那真没什么问题——简单、直接、任何一个新人都能看懂。但现实是,这种貌似“简单”的代码往往会在三个月后变成这样:
java复制public double calculatePrice(String userType, double originalPrice, int orderCount, boolean isFirstOrder, String couponId, ...) {
if ("vip".equals(userType)) {
if (orderCount > 10) {
return originalPrice * 0.85;
}
return originalPrice * 0.9;
} else if ("enterprise".equals(userType)) {
if (isFirstOrder) {
// 和优惠券逻辑纠缠在一起
}
return originalPrice * 0.8;
} else {
// 普通用户又拆出新客、老客、复购等多种情况
}
}
你会发现,每个新需求都在往这个函数里塞新的参数、新的分支。代码的“圈复杂度”越来越高,逻辑开始纠缠,测试用例越来越难写,而真正可怕的还不是这里——而是当“是否有首单优惠”这个判断被复制到了订单列表、购物车、结算页三个地方时,你改一个逻辑漏改另一个,线上就出事。
1.2 策略模式提供的是一种“封闭的变化点”思维
策略模式的核心思想可以概括成一句话:把算法(或者说行为)封装成独立的策略对象,让使用算法的“上下文”不感知具体算法,从而可以用组合的方式在运行时替换算法。
这里的关键不是“少写分支”,而是“把分支消灭在对象的创建阶段”。分支仍然存在——你总得决定创建哪一个策略对象——但它只出现一次,而不是散落在业务逻辑的各个角落。这就是所谓的“封装变化点”。
我用一个生活化的类比来解释。你去餐厅吃饭,菜单上有红烧肉、清蒸鱼、麻婆豆腐。如果餐厅把“点菜”这个动作和“做菜”的过程写在同一个本子上,每增加一道新菜就要改一次点菜流程。而策略模式的做法是:菜单负责“展示菜名和价格”,后厨负责“实际做菜”,两者通过一个“下单”接口对接。客人选了红烧肉,菜单把这个选择传递给做红烧肉的厨师,仅此而已。就算后厨明天换一个厨师,菜名和价格不用跟着变。
这个过程里,发生变化的是“菜”——也就是算法;保持稳定的是“菜单”——也就是上下文。策略模式为我们提供了一条清晰的边界:那些未来可能扩展的算法,应该被拿出来独立演化和测试。
1.3 什么时候你不需要策略模式
正因为策略模式被过度神化了,我得先泼一盆冷水。如果你的代码只有两三个分支,而且未来几年内都不太可能扩展,那就老老实实写 if。设计模式不是勋章,每多一层抽象,就多一层学习成本和间接跳转成本。真正的高手懂得在“简单”和“可扩展”之间找平衡。
我还见过一种反模式:一个接口只有一个实现类,也硬要套上策略模式,理由是“万一以后有第二个实现呢”。这种“万一”如果持续三年都没有发生,那这三年的代码就是在为一个从未出现的需求付房租。更好的做法是:当第二个相似场景出现时,再顺手重构。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 策略模式的经典实现拆解:一份能直接抄作业的 Java 和 C++ 对照
讲完理论,下面给一个可以照着写的完整实现。我会用“支付方式”这个场景来演示,因为它比报价系统更贴近大多数人日常写的业务代码。需求是:一个订单支持支付宝、微信、银行卡三种支付方式,未来可能还会加“余额支付”。
2.1 首先是策略接口和三个具体策略
不管是 Java 还是 C++,策略模式的结构都一样:一个策略接口(或抽象类)、若干具体策略类、一个持有策略引用的上下文。先看 Java 版:
java复制// 策略接口:所有支付方式的统一契约
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
// 具体策略:支付宝支付
public class AlipayStrategy implements PaymentStrategy {
private String alipayAccount;
public AlipayStrategy(String alipayAccount) {
this.alipayAccount = alipayAccount;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("使用支付宝账户 " + alipayAccount + " 支付 " + amount + " 元");
// 实际的支付宝 SDK 调用逻辑写在这里
}
}
// 具体策略:微信支付
public class WechatPayStrategy implements PaymentStrategy {
private String openId;
public WechatPayStrategy(String openId) {
this.openId = openId;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("使用微信账户 " + openId + " 支付 " + amount + " 元");
}
}
// 具体策略:银行卡支付
public class BankCardStrategy implements PaymentStrategy {
private String cardNo;
public BankCardStrategy(String cardNo) {
this.cardNo = cardNo;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("使用银行卡 " + cardNo + " 支付 " + amount + " 元");
}
}
C++ 版的写法稍有差异,核心差别在于 C++ 需要你自己管理对象生命周期,或者用智能指针:
cpp复制#include <iostream>
#include <memory>
#include <string>
// 策略接口
class PaymentStrategy {
public:
virtual ~PaymentStrategy() = default;
virtual void pay(double amount) const = 0;
};
// 支付宝策略
class AlipayStrategy : public PaymentStrategy {
public:
explicit AlipayStrategy(std::string account) : account_(std::move(account)) {}
void pay(double amount) const override {
std::cout << "使用支付宝账户 " << account_
<< " 支付 " << amount << " 元" << std::endl;
}
private:
std::string account_;
};
// 微信支付策略
class WechatPayStrategy : public PaymentStrategy {
public:
explicit WechatPayStrategy(std::string openId) : open_id_(std::move(openId)) {}
void pay(double amount) const override {
std::cout << "使用微信账户 " << open_id_
<< " 支付 " << amount << " 元" << std::endl;
}
private:
std::string open_id_;
};
这些策略类本身很“薄”,只是演示结构。真实项目里,每个策略类内部通常会调用不同的 SDK、处理不同的回调、计算不同的手续费,这才是让策略类体积膨胀的真正原因。
2.2 上下文类:策略的持有者和调用者
定义完了策略,接下来是“上下文”(Context)。它的职责是持有一个策略引用,并对外提供统一的调用入口。注意:上下文本身不关心策略的具体类型,只认接口。这在 Java 里可以是这样的:
java复制public class PaymentService {
private PaymentStrategy strategy;
// 运行期替换策略
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void payOrder(BigDecimal amount) {
if (strategy == null) {
throw new IllegalStateException("支付策略未设置");
}
strategy.pay(amount);
}
}
C++ 版本通常会用一个 std::shared_ptr<PaymentStrategy> 或者 std::unique_ptr<PaymentStrategy> 来持有策略对象,避免裸指针带来的内存管理问题:
cpp复制class PaymentService {
public:
void setStrategy(std::shared_ptr<PaymentStrategy> strategy) {
strategy_ = std::move(strategy);
}
void payOrder(double amount) const {
if (!strategy_) {
throw std::runtime_error("支付策略未设置");
}
strategy_->pay(amount);
}
private:
std::shared_ptr<PaymentStrategy> strategy_;
};
看到这里你可能会想:这比 if-else 优雅在哪?从“调用方”的角度看,payOrder 方法完全不知道今天是支付宝还是银行卡,它只面向 PaymentStrategy 接口编程。支付宝的策略改逻辑,不会影响微信的代码;新增一个“余额支付”,也只需要新增一个类,PaymentService 一行都不用动。这就是开闭原则在起作用。“对扩展开放,对修改关闭”这一条,策略模式落实得非常具体。
2.3 跑起来:客户端如何组装
有了策略和上下文,客户端的使用方式就是这样:
java复制public class Client {
public static void main(String[] args) {
PaymentService service = new PaymentService();
// 根据用户选择设置不同的策略
if (userChoosesAlipay) {
service.setStrategy(new AlipayStrategy("user@example.com"));
} else if (userChoosesWechat) {
service.setStrategy(new WechatPayStrategy("wx-openid-12345"));
} else if (userChoosesBankCard) {
service.setStrategy(new BankCardStrategy("6222 **** **** 1234"));
}
service.payOrder(new BigDecimal("199.00"));
}
}
注意,这里依然有 if-else。策略模式并没有消灭分支,它只是把分支从“业务计算逻辑”里推到了“对象组装”的边界上。所以那些吹嘘“策略模式零 if-else”的人,基本都是没写过真实项目。真正意义上的“消灭分支”是靠注册表或工厂来做的,这属于策略模式的高级用法,后面会细讲。
2.4 一个更容易被忽略的问题:策略对象怎么创建
我之前看过很多代码,策略模式本身写得没问题,但客户端的 if-else 从 3 个分支膨胀到了 20 个分支,因为每新增一种支付方式,客户端代码就要跟着改。这其实已经违背了开闭原则——所有变化都集中到了一个“创建策略”的 switch 里。
比较常见的解法是引入一个“策略工厂”:
java复制public class PaymentStrategyFactory {
private final Map<String, PaymentStrategy> strategies = new HashMap<>();
public PaymentStrategyFactory() {
// 在构造阶段注册所有策略
strategies.put("alipay", new AlipayStrategy("default@example.com"));
strategies.put("wechat", new WechatPayStrategy("default-openid"));
strategies.put("bankcard", new BankCardStrategy("default-card"));
}
public PaymentStrategy getStrategy(String type) {
PaymentStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式: " + type);
}
return strategy;
}
}
工厂的好处是把“创建哪个策略”的决策集中在一处,客户端只需要传一个字符串就拿到对应的策略对象。如果某个策略依赖外部配置,也可以在工厂的初始化阶段装配好。你会注意到,这里策略对象的注册是通过一个 Map 来做的,这就是“注册表”的思路。再进一步,你甚至可以扫描类路径、读取配置来动态注册策略,这样连工厂的代码都不用改了。
3. 策略模式与状态模式、工厂模式的边界:你能分清楚吗
设计模式学到最后,大家都会卡在“某些模式很像,到底该用哪个”的困惑上。这里最容易混的就是“策略模式”和“状态模式”,其次是“策略模式”和“工厂模式”。一旦用错,代码的维护成本会直接翻倍。我分别讲清楚它们各自关心的核心问题。
3.1 策略模式 vs 状态模式:一个“随缘切换”,一个“自动流转”
策略模式的本质是“封装算法”,强调的是“可选行为之间的平级替换”。比如支付方式:支付宝和微信是平级的,不会因为支付完了就自动变成另一种支付。上下文中的策略是由外部指定的——用户选择了什么,就用什么,用完之后策略一般不会变。
状态模式则完全不同,它封装的是“状态”,强调的是“随着内部状态的变化,行为自动切换”。经典的例子是订单状态机:待支付、已支付、已发货、已完成。每一个状态对应一个行为集合,当订单状态从“待支付”变成“已支付”时,状态对象会替换成下一个状态对象,而这种替换是状态的内部流转驱动,不是外部调用者指定的。
这么说可能还抽象,我举个例子。假设你有个“订单”类,它有一个“当前状态”的引用,然后调用 getStatus() 或 proceed() 方法时,内部会执行当前状态的逻辑,并决定下一个状态是谁。
java复制// 状态模式简化示意
public interface OrderState {
void handle(OrderContext context);
}
public class PendingState implements OrderState {
@Override
public void handle(OrderContext context) {
// 待支付状态下的行为...
context.setState(new PaidState()); // 状态自己决定流转
}
}
对比一下,策略模式里的算法不会主动“切换到下一个算法”。支付宝策略执行完就完了,它不会说“哎,我支付完了,你切换成微信吧”。这就是最直观的判断标准:如果行为是平级且使用者可以自由选择的,考虑策略模式;如果行为会因状态变化而自动迁移,考虑状态模式。
不过实践中还有一个比较隐蔽的情况:状态模式通常会持有上下文的引用,以便回写状态;而策略模式一般不需要持有上下文引用,只专注于算法本身。如果一个策略类里频繁地出现“调用上下文的方法”,那大概率不是策略模式,而是某种状态模式或回调模式,需要警惕设计意图是否已经走偏。
3.2 策略模式 vs 工厂模式:一个是“创建对象”,一个是“使用算法”
工厂模式的关注点是“对象的创建”,它解决的是“new 谁”的问题。策略模式的关注点是“算法的使用”,它解决的是“执行什么行为”的问题。所以严格来说,工厂模式和策略模式并不在同一维度,它们经常配合使用,但不应该互相替代。
很多人把“策略工厂”误认为“策略模式”,代码里写了一个 PaymentStrategyFactory 就觉得已经用了策略模式——其实这个类是工厂模式,提供策略接口和具体策略实现的才是策略模式。这当然不是错误,反而是一种很好的实践——用工厂去屏蔽策略对象的创建细节。但要理解设计意图,两者解决的问题不同。
如果你打算用策略模式去解决“参数太多导致构造函数复杂”的问题,那就是用错了工具。这种情况更应该考虑建造者模式或参数对象。同理,如果你用策略模式去控制“某些策略只允许创建一次”,那应该引入享元模式或单例模式,而不是在策略模式内部硬扛。
3.3 策略模式 vs 模板方法模式:一个靠组合,一个靠继承
模板方法模式也是经常和策略模式一起出现的“亲戚”,但它们的实现机制完全不同。模板方法模式是在父类中定义好算法骨架,把其中某些步骤延迟到子类实现;策略模式则是把整个算法封装成独立的对象,通过组合的方式塞进上下文。
从类关系上讲,模板方法用的是继承,策略模式用的是组合。组合优于继承这个原则在现代工程里几乎已经是常识了,因为继承会让父类和子类之间产生脆弱的耦合,父类改动一点点,所有子类都会受影响。而组合则是把算法对象和上下文解耦,两边各自独立演进。
实战里经常能看到这两种模式混合使用:策略接口本身是抽象的,内部用模板方法模式定义了执行流程,具体策略在子类中补齐差异。我个人不太建议这么用,因为层层抽象会显著增加阅读成本。如果策略之间真有很大一部分公共逻辑,更好的做法是把公共逻辑拆成独立的工具类或基类,而不是用模板方法在策略内部套娃。
3.4 策略模式的“变体”:枚举策略
前面提到过“策略对象怎么创建”的问题,有一种很优雅的变体是用枚举来实现策略,在 Java 里尤其好用。每个枚举常量本身就相当于一个单例的策略对象,同时还能通过枚举字段保存数据。这个写法我在真实项目里用过,很适合策略数量较少且不需要外部配置的场景。
java复制public enum PaymentStrategy {
ALIPAY {
@Override
public void pay(BigDecimal amount) {
// 支付宝支付逻辑
}
},
WECHAT {
@Override
public void pay(BigDecimal amount) {
// 微信支付逻辑
}
},
BANK_CARD {
@Override
public void pay(BigDecimal amount) {
// 银行卡支付逻辑
}
};
public abstract void pay(BigDecimal amount);
}
调用起来更简洁:
java复制PaymentStrategy strategy = PaymentStrategy.valueOf(paymentTypeStr);
strategy.pay(amount);
这种方式的优点是:策略对象是天然单例,工厂逻辑都省了,代码结构紧凑;缺点是:不够灵活,如果策略需要传入大量运行时参数(比如支付宝账号、银行卡号),枚举策略就有点别扭,因为你没法在每次使用时创建不同参数的实例。所以这个变体更适合“无状态”的策略,比如折扣计算、日志上报、文件格式转换这类场景。
4. 真实项目里的策略模式:如何取舍、测试、维护,避免过度设计
前面都是理论拆解,这一节我想讲点实战中的体感。毕竟设计模式这种东西,背概念没有意义,真正有价值的是在真实项目里踩过的坑和总结出来的判断标准。
4.1 配置驱动策略:把选择权交给运行时
前面提到用工厂包装策略创建,但这还不是“终极形态”。在真实分布式系统里,追求“新增一个策略不修改一行代码”的关键手段,是“配置驱动”。让策略的类型、参数、优先级都脱离代码,成为配置的一部分,这样即使不发布新版本也能调整行为。
我曾经在一个订单系统里做过这样的改造。当时需要支持多种“分摊优惠金额”的算法,每个商家通过后台配置选择算法类型。第一版代码写了一个大工厂,每次新增算法都要改代码。后来我改成“基于操作码注册”的方式:策略类上增加一个注解,标记自己的策略类型和版本号,系统启动时扫描所有标注了注解的类,把它们注册到一个全局 Map<String, PaymentStrategy> 里。这样新增一个策略只需要增加一个类文件,工厂代码完全不动。这其实已经接近“插件化架构”的味道了。
java复制@StrategyType(type = "alipay_v2")
public class AlipayV2Strategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
// v2 逻辑
}
}
注册表的实现很简单,核心逻辑就是利用反射或ServiceLoader扫描类路径,找到所有标注了注解的策略类并实例化,放进Map中。如果你用的是 Spring,那更简单——直接把策略类注入到一个Map<String, PaymentStrategy>里,Spring 的 @Autowired 会自动搞定按名字注入:
java复制@Service
public class PaymentService {
private final Map<String, PaymentStrategy> strategyMap;
public PaymentService(Map<String, PaymentStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
public void pay(String strategyType, BigDecimal amount) {
PaymentStrategy strategy = strategyMap.get(strategyType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的策略类型: " + strategyType);
}
strategy.pay(amount);
}
}
这样写有个巨大的好处:新增支付方式时,你在原来的业务代码里什么都不用动,只需要新增一个 @Component 类。对于一个频繁扩容的商业系统来说,这种“对修改关闭,对扩展开放”的能力至关重要。当然代价是,代码的“魔法感”变强了——新人接手的时候,光看业务代码找不到分支,会很不适应。这就需要在代码结构、注释、文档上做相应的补偿。
4.2 策略模式测试的三种关键写法
工程实践里,策略模式的测试有一个很容易踩的坑:因为策略模式把分支分散到了各个策略类,测试人员很容易只测了某一个策略就认为“模块测完了”。但实际上,真正需要测试的有三层。
第一层是每个策略类的单体测试。每个策略都必须独立覆盖自己的正常路径、异常路径和边界值。比如支付宝支付策略,你至少要测:金额为正、金额为零、金额为负(预期报错)、SDK 返回失败时的处理、SDK 超时时的处理。这一层测试跑起来要非常快,而且要尽量 mock 掉外部依赖。
第二层是上下文(Context)的测试。关键点是验证上下文和策略之间交互是否正确——策略设置不上时有没有报错、切换策略后行为是否真的变了。这一层最容易被忽略,但它恰恰能捕捉到“策略被遗忘设置”这种隐患。
第三层是集成测试。如果策略工厂是通过注册表 + 反射扫描实现的,那么还要测试“新增策略类后是否正确注册”、“同类型策略重复注册时怎么处理”、“非法类型字符串有没有给出友好提示”。
还有一个常被忽略的测试点:策略对象是否符合“单例预期”。如果你用了无状态的策略类,但配置成了多例,高并发时可能会频繁创建对象造成内存压力;反过来,如果策略类内部存了用户态的字段而不是线程安全的,又可能在并发场景下互相污染。所以策略类的状态管理要非常小心。我的一条经验是:策略类本身尽量保持无状态,需要输入参数时通过方法参数传入,而不是在编译时塞进策略的字段里。 如果确实需要传参,优先考虑把参数包装成对象传入方法,而不是在构造策略时固定。
4.3 过度设计预警:这些信号说明你其实不需要策略模式
我见过太多“为了设计模式而设计模式”的代码。一个简单的工具函数,也要抽象一个 Handler 接口、写三个实现、配一个工厂、再套一层代理,最后调用方还得查文档才知道该用哪个实现。这种代码的维护成本和认知负担,远远超过了它带来的扩展性收益。
总结了一下,以下几种信号出现时,你大概率是在滥用策略模式:
第一,目前只有一个策略实现,而且第二个实现遥遥无期。这时候接口和实现分离的唯一理由,可能是为了mock测试,那就宁可让接口更小更聚焦,也不要虚构一个“高扩展性”的幌子。
第二,策略的变化频率极低,一年到头都不会新增或修改算法。比如你有一个“计算两点距离”的算法,只有欧氏距离一种方式,十年都没变过,那完全没有必要定义 DistanceStrategy 接口。
第三,策略的粒度太细,细到每个“策略”只有三行代码。这种情况下引入策略模式只会增加类爆炸,一个策略类文件十几行,却要创建十几个类,项目结构瞬间变成“类海”。这种情况下,一个 enum 加 switch 也许就足够了。
第四,策略的“切换”不是在运行期发生的,而是在编译期就确定了。比如同一套代码在 A 和 B 两个部署环境里固定使用不同的加解密算法,那用一个简单的 if (env.equals("A")) 就够了,运行期切换的能力根本用不上。
判断“到底要不要用策略模式”,我的标准比较朴素:问题域里是否真实存在“多个可替换的算法/行为”,并且这些算法/行为的扩展频率是否值得为它们建立一层抽象。 如果答案是“会经常扩展”,那策略模式值得投入;如果答案是“大概率不会”,那简化代码比迎合未来的想象更重要。
4.4 结合最新的子代理设计范式:多Agent系统里的策略模式变体
最近“多Agent设计”特别热,尤其是“主从模式(Master-Slave / Supervisor-Subagent)”被反复提及。很多人第一反应是“这是不是一个全新的架构思想”,其实扒开看,它的底层逻辑和策略模式出奇地相似,可以说是一种“换了马甲”的经典思想。
在多Agent系统里,主Agent(Master/Supervisor)负责分解任务、做决策、调度资源;子Agent(Subagent)负责执行具体的子任务。和传统策略模式不同的是,这里的“策略”不再是单纯的算法函数,而是一个拥有语义理解能力、上下文记忆和工具调用权限的Agent。但本质上,主Agent调用子Agent的方式,和策略模式中上下文调用策略接口几乎是一模一样的:
- 主Agent持有一个“能力注册表”,类似于策略工厂的注册表;
- 子Agent对外暴露统一的任务接口(输入任务描述,输出结果),类似策略接口;
- 主Agent根据任务类型或用户的请求,动态选择最合适的子Agent,类似运行期切换策略。
有人甚至提出一个新视角:把子Agent视为一种“另类的工具(Tool)”。这个说法很精辟。在Agent的抽象框架里,工具就是一组“输入→输出”的函数,子Agent本质上也是这种函数——给定一个任务描述,返回一个结果。调用工具和调用子Agent,在调用方的接口层面是没有本质区别的。这意味着什么?意味着你在设计一个多Agent系统时,完全可以用策略模式(或注册表模式)来组织你的“子Agent列表”,用户请求进来时,根据意图识别动态选择一个或多个子Agent执行。
具体到工程上,最实用的设计是:给所有子Agent定义一个统一的函数签名,让它们接收“目标、上下文、可用工具、历史消息”等参数,再返回“结果、状态、耗时”等结构。主Agent只依赖这个统一签名,不关心具体子Agent的实现细节。这样新加一个子Agent,只需要实现这个签名并注册到Agent工厂,系统其他部分一行不用改。这种设计思路,策略模式里已经沉淀下非常成熟的套路了。
从这一点看,设计模式并没有过时。相反,很多新架构、新概念其实都是经典思想在新的技术容器里的重新演绎。掌握策略模式,不只是多会一个“设计姿势”,更是帮你在面对新东西时,更快看穿它的本质结构。这个能力,才是比代码本身更有价值的收获。
