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框架,我建议这样用策略模式:

  1. 先定义策略接口,方法参数和返回值要统一
  2. 在控制器初始化时,注册所有策略
  3. 请求进来后,根据请求参数或路由,快速定位策略
  4. 执行策略,返回结果

我曾经在一个项目中,把策略注册做成了自动发现。只要在指定目录下新建一个策略类,框架会自动扫描并注册。这样连手动注册都省了,开发效率提升不少。

最后说一句:策略模式不是银弹。如果你的业务逻辑变化不大,或者策略之间差异很小,用策略模式反而增加复杂度。但如果你面对的是「多种请求、多种处理方式、经常扩展」的场景,策略模式绝对是MVC控制器的好搭档。

MVC策略模式流程图 客户端请求 控制器(上下文) 策略映射表 策略接口 登录策略 LoginStrategy 注册策略 RegisterStrategy 找回密码策略 ForgotStrategy 请求 控制器 策略接口 具体策略

这张图展示了策略模式在MVC中的完整流程。客户端请求到达控制器,控制器查策略映射表,找到对应的具体策略,然后执行。每个策略只负责自己的逻辑,互不干扰。

一句话总结:策略模式让控制器从「执行者」变成「调度者」,把变化封装在策略里,把稳定留给控制器。

公众号:蓝海资料掘金营,微信deep3321