12、MVC中的策略模式:控制器如何根据请求选择不同策略
策略模式,说白了就是「把算法封装起来,让它们可以互相替换」。在MVC里,控制器经常要面对各种不同的请求——用户登录、商品搜索、订单提交……每个请求的处理逻辑都不一样。如果全写在控制器里,那代码会膨胀得像个气球。
我早期做项目时,就踩过这个坑。一个控制器里塞了十几个if-else,每次加新功能都要改这个文件。后来重构时用了策略模式,瞬间清爽了。你想想看,把每个请求的处理逻辑独立出来,控制器只负责「选谁干活」,这不就是策略模式的核心吗?
12.1 策略模式的核心思想
策略模式有三个角色:
- 上下文(Context):持有策略对象的引用,负责调用策略。在MVC里,控制器就是上下文。
- 策略接口(Strategy):定义所有策略必须实现的方法。比如一个
handle()方法。 - 具体策略(Concrete Strategy):实现策略接口,封装具体的业务逻辑。
举个例子:用户点击「登录」和「注册」两个按钮,请求都发到同一个控制器。控制器根据请求参数,选择不同的策略来处理。
核心要点:策略模式让控制器不再关心「怎么做」,只关心「选谁做」。
12.2 控制器如何选择策略
我习惯在控制器里维护一个策略映射表。请求进来时,根据请求类型(比如URL参数、HTTP方法、请求头)查表,找到对应的策略对象,然后执行。
来看一段伪代码:
// 策略接口
interface RequestStrategy {
function handle($request);
}
// 具体策略:登录
class LoginStrategy implements RequestStrategy {
public function handle($request) {
// 验证用户名密码
// 生成token
// 返回响应
}
}
// 具体策略:注册
class RegisterStrategy implements RequestStrategy {
public function handle($request) {
// 校验参数
// 创建用户
// 发送欢迎邮件
}
}
// 控制器(上下文)
class UserController {
private $strategies = [];
public function __construct() {
$this->strategies['login'] = new LoginStrategy();
$this->strategies['register'] = new RegisterStrategy();
}
public function handleRequest($request) {
$action = $request->getParam('action');
if (!isset($this->strategies[$action])) {
throw new Exception('未知请求');
}
return $this->strategies[$action]->handle($request);
}
}
你看,控制器里没有if-else,只有一行查表和一行动态调用。新加一个「找回密码」功能,只需要新增一个策略类,注册到映射表里就行。控制器完全不用改。
我的经验:策略映射表可以用配置文件或数据库来管理,这样连代码都不用改,改配置就能切换策略。我在一个电商项目里就这么干过,运营同学自己配规则,爽得很。
12.3 策略模式在MVC中的典型场景
不只是用户请求,策略模式在MVC里还有很多用武之地:
| 场景 | 策略划分依据 | 示例 |
|---|---|---|
| 支付方式 | 支付渠道(微信、支付宝、银行卡) | 不同支付策略处理不同的回调逻辑 |
| 数据导出 | 导出格式(CSV、Excel、PDF) | 每种格式一个策略,控制器根据参数选择 |
| 消息推送 | 推送渠道(短信、邮件、App推送) | 策略模式封装不同渠道的API调用 |
| 权限校验 | 用户角色(管理员、普通用户、访客) | 不同角色走不同的校验策略 |
我曾经在一个报表系统里,把数据源也做成了策略模式。同一个报表,可以从MySQL、Redis、Elasticsearch三个数据源获取数据。控制器根据请求参数选择数据源策略,完全不用改报表逻辑。
12.4 策略模式 vs 简单工厂
很多人会把策略模式和简单工厂搞混。我简单说一下区别:
- 简单工厂:创建对象。工厂根据参数返回不同的对象实例。
- 策略模式:封装行为。上下文持有策略对象,调用策略的方法。
说白了,工厂是「造东西的」,策略是「干活的」。在MVC里,控制器可以先用工厂创建策略对象,再用策略模式执行策略。两者不冲突,反而经常配合使用。
注意:策略模式不要滥用。如果只有两三种策略,而且不太可能扩展,直接用if-else反而更简单。我见过有人为了用模式而用模式,把简单的逻辑搞得复杂无比。嗯,这其实是个坑。
12.5 策略模式的优缺点
优点很明显:
- 开闭原则:新增策略不用改现有代码
- 消除条件判断:控制器不再有一大堆if-else
- 提高可测试性:每个策略可以单独测试
缺点也得知道:
- 类数量增加:每个策略一个类,小项目可能觉得啰嗦
- 调用方必须知道所有策略:控制器需要知道有哪些策略可用
- 策略之间可能有依赖:比如登录策略和注册策略都用到用户模型,要注意解耦
12.6 实战建议
如果你正在设计一个MVC框架,我建议这样用策略模式:
- 先定义策略接口,方法参数和返回值要统一
- 在控制器初始化时,注册所有策略
- 请求进来后,根据请求参数或路由,快速定位策略
- 执行策略,返回结果
我曾经在一个项目中,把策略注册做成了自动发现。只要在指定目录下新建一个策略类,框架会自动扫描并注册。这样连手动注册都省了,开发效率提升不少。
最后说一句:策略模式不是银弹。如果你的业务逻辑变化不大,或者策略之间差异很小,用策略模式反而增加复杂度。但如果你面对的是「多种请求、多种处理方式、经常扩展」的场景,策略模式绝对是MVC控制器的好搭档。
这张图展示了策略模式在MVC中的完整流程。客户端请求到达控制器,控制器查策略映射表,找到对应的具体策略,然后执行。每个策略只负责自己的逻辑,互不干扰。
一句话总结:策略模式让控制器从「执行者」变成「调度者」,把变化封装在策略里,把稳定留给控制器。