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中的完整工作流程。这张图展示了从请求进入到最后返回响应的全过程。
从这张图可以看得很清楚:控制器不直接new模型和视图,而是通过工厂去获取。这样做的好处是,哪天你想换一个模型实现,只需要改工厂,控制器代码纹丝不动。
实际项目中的最佳实践
我在好几个大型项目中都用过这套方案,总结出几条经验:
| 场景 | 推荐模式 | 原因 |
|---|---|---|
| 模型类型少于5种 | 简单工厂 | 代码量少,够用 |
| 视图需要根据设备切换 | 工厂方法 | 子类灵活扩展 |
| 控制器有多个版本(Web/API) | 抽象工厂 | 保证版本一致性 |
| 对象创建逻辑复杂 | 建造者模式+工厂 | 分步构建更清晰 |
我的建议:不要为了用工厂而用工厂。如果你的项目只有三个模型、两个视图,直接new反而更清晰。工厂模式的价值在规模效应中体现——当你的项目有几十个类需要创建时,工厂就是你的救星。
避坑指南
最后分享几个我踩过的坑:
- 过度抽象:我曾经在一个小项目里用了三层工厂,结果新同事看了三天没看懂。记住:简单工厂能解决的问题,别上抽象工厂。
- 工厂变成上帝类:把所有对象的创建都塞到一个工厂里,这个工厂会变得又臭又长。我建议按模块拆分工厂,比如UserModuleFactory、OrderModuleFactory。
- 忽略依赖注入:工厂里硬编码new对象,导致单元测试没法mock。我现在的做法是:工厂接收容器,由容器来实例化对象。
嗯,工厂模式在MVC中的应用,说白了就是让对象的创建变得可控、可维护。你想想看,当你的项目从几百行膨胀到几万行时,有一个统一的创建入口,能省下多少排查问题的时间。