14、MVC中的工厂模式:讲解工厂模式在MVC中的应用,如何创建模型、视图和控制器对象。

工厂模式,说白了就是帮你“生产对象”的。在MVC架构里,我们天天要new模型、new视图、new控制器。如果这些创建逻辑散落在各处,后期改起来会非常痛苦。我个人习惯,只要发现一个类被new了三次以上,就会考虑用工厂模式封装一下。

为什么MVC需要工厂模式?

你想想看,一个典型的MVC应用里,模型可能有几十种,视图可能有上百种,控制器也有几十种。如果每个地方都直接new,一旦某个类的构造函数变了,你得满世界找调用点去改。我在项目中遇到过这种情况:产品经理要求给所有模型加一个缓存参数,结果我改了三天,改了三十多个文件,还漏了两个,线上出了bug。

工厂模式的核心价值就三点:

  • 解耦:调用者不需要知道对象怎么创建的
  • 集中管理:所有创建逻辑都在一个地方
  • 灵活扩展:加新类型时,只改工厂,不改调用方

核心原则:不要在你的控制器里直接new Model(),让工厂去干这个脏活累活。

工厂模式在MVC中的三种应用场景

根据我多年的架构经验,工厂模式在MVC里主要有三种玩法。咱们一个一个来看。

1. 简单工厂:创建模型对象

模型对象通常对应数据库表或业务实体。不同模型可能有不同的初始化参数。我曾经接手过一个老项目,里面到处都是new UserModel($db)、new OrderModel($db, $cache),后来统一改成工厂,清爽多了。

class ModelFactory {
    public static function create($type, $db, $cache = null) {
        switch ($type) {
            case 'user':
                return new UserModel($db);
            case 'order':
                return new OrderModel($db, $cache);
            case 'product':
                return new ProductModel($db);
            default:
                throw new Exception("未知模型类型: {$type}");
        }
    }
}

// 使用方式
$userModel = ModelFactory::create('user', $db);
$orderModel = ModelFactory::create('order', $db, $cache);

小技巧:如果模型需要依赖注入,可以在工厂里统一组装依赖,调用方只需要传一个类型名就够了。

2. 工厂方法:创建视图对象

视图的创建往往更复杂。同一个数据,可能要根据设备类型(PC、手机、平板)渲染不同的视图。我建议用工厂方法模式,让子类决定实例化哪个视图。

abstract class ViewFactory {
    abstract public function createView($data);
    
    public function render($data) {
        $view = $this->createView($data);
        return $view->render();
    }
}

class MobileViewFactory extends ViewFactory {
    public function createView($data) {
        return new MobileView($data);
    }
}

class DesktopViewFactory extends ViewFactory {
    public function createView($data) {
        return new DesktopView($data);
    }
}

// 使用方式
$factory = new MobileViewFactory();
$html = $factory->render($userData);

嗯,这里要注意:工厂方法模式比简单工厂更灵活,但类数量会翻倍。如果你的视图类型不超过五种,简单工厂就够了。

3. 抽象工厂:创建控制器家族

控制器往往不是单独存在的。一个模块通常包含一组控制器:UserController、AdminController、ApiController。抽象工厂可以帮你创建一整套控制器家族。

interface ControllerFactory {
    public function createUserController();
    public function createAdminController();
    public function createApiController();
}

class WebControllerFactory implements ControllerFactory {
    public function createUserController() {
        return new WebUserController();
    }
    public function createAdminController() {
        return new WebAdminController();
    }
    public function createApiController() {
        return new WebApiController();
    }
}

class ApiControllerFactory implements ControllerFactory {
    public function createUserController() {
        return new ApiUserController();
    }
    public function createAdminController() {
        return new ApiAdminController();
    }
    public function createApiController() {
        return new ApiApiController();
    }
}

// 使用方式
$factory = new WebControllerFactory();
$userCtrl = $factory->createUserController();

避坑指南:我曾经在一个项目里把抽象工厂用得太复杂,导致加一个新控制器要改四个文件。记住:抽象工厂适用于“产品族”概念,如果你的控制器之间没有强关联,用简单工厂就够了。

工厂模式在MVC中的完整流程

咱们用一张图来梳理一下工厂模式在MVC中的完整工作流程。这张图展示了从请求进入到最后返回响应的全过程。

工厂模式在MVC中的工作流程 客户端请求 路由分发 控制器工厂 根据请求类型创建对应控制器 控制器 模型工厂 模型 视图工厂 视图 返回响应 MVC组件 工厂

从这张图可以看得很清楚:控制器不直接new模型和视图,而是通过工厂去获取。这样做的好处是,哪天你想换一个模型实现,只需要改工厂,控制器代码纹丝不动。

实际项目中的最佳实践

我在好几个大型项目中都用过这套方案,总结出几条经验:

场景 推荐模式 原因
模型类型少于5种 简单工厂 代码量少,够用
视图需要根据设备切换 工厂方法 子类灵活扩展
控制器有多个版本(Web/API) 抽象工厂 保证版本一致性
对象创建逻辑复杂 建造者模式+工厂 分步构建更清晰

我的建议:不要为了用工厂而用工厂。如果你的项目只有三个模型、两个视图,直接new反而更清晰。工厂模式的价值在规模效应中体现——当你的项目有几十个类需要创建时,工厂就是你的救星。

避坑指南

最后分享几个我踩过的坑:

  • 过度抽象:我曾经在一个小项目里用了三层工厂,结果新同事看了三天没看懂。记住:简单工厂能解决的问题,别上抽象工厂。
  • 工厂变成上帝类:把所有对象的创建都塞到一个工厂里,这个工厂会变得又臭又长。我建议按模块拆分工厂,比如UserModuleFactory、OrderModuleFactory。
  • 忽略依赖注入:工厂里硬编码new对象,导致单元测试没法mock。我现在的做法是:工厂接收容器,由容器来实例化对象。

嗯,工厂模式在MVC中的应用,说白了就是让对象的创建变得可控、可维护。你想想看,当你的项目从几百行膨胀到几万行时,有一个统一的创建入口,能省下多少排查问题的时间。

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