Appearance
2.1 工厂模式 (Factory Pattern)
恭喜你!我们正式开始学习第一个设计模式。工厂模式是创建型模式中最基础、最常见的一种,它的核心思想非常简单:将创建对象的过程单独封装起来。
一、生活中的场景:去咖啡店点单
想象一下,你想喝一杯“拿铁”。你通常不会自己去买咖啡豆、牛奶,然后研究如何打奶泡、控制萃取时间。
你会怎么做?走进一家咖啡店,对店员说:“你好,一杯拿铁。”
在这个场景中:
- 你:是“使用者”(Client)。你只关心得到一杯拿铁,不关心如何制作。
- 店员/咖啡机:就是“工厂”(Factory)。它接收你的“拿铁”指令,然后负责所有复杂的创建过程。
- 拿铁:是“产品”(Product)。
如果明天你想喝“美式”,你同样是告诉店员一声,由他这个“工厂”来为你制作。你作为使用者,完全被从复杂的创建过程中解耦出来了。
这就是工厂模式最核心的理念。
二、没有工厂时的代码:重复与僵化
假设我们正在开发一个简单的用户管理系统,需要根据不同的用户类型(如普通用户、管理员)创建不同的用户对象,并赋予它们不同的权限视图。
javascript
// 定义两个用户类
class User {
constructor(name) {
this.name = name;
this.viewPage = ["首页", "商品列表"];
}
}
class Admin {
constructor(name) {
this.name = name;
this.viewPage = ["首页", "商品列表", "用户管理", "订单管理"];
}
}
// 在某个页面逻辑中,我们需要根据角色创建实例
function render(role, name) {
let user;
if (role === "user") {
user = new User(name);
} else if (role === "admin") {
user = new Admin(name);
}
// ... 接下来是使用 user 实例渲染页面的逻辑
console.log(`${user.name} 的权限页面: ${user.viewPage.join(", ")}`);
}
render("admin", "张三"); // 张三 的权限页面: 首页, 商品列表, 用户管理, 订单管理
render("user", "李四"); // 李四 的权限页面: 首页, 商品列表这段代码有什么问题?
- 创建与使用耦合:
render函数本应只关心“渲染”逻辑,但现在它被迫要知道User和Admin这两个具体类的存在,以及如何用new来创建它们。创建过程和使用过程紧紧地绑在了一起。 - 难以扩展:如果未来新增一个
SuperAdmin(超级管理员)角色,我们必须修改render函数内部的if...else逻辑。这违反了我们之前提到的“开放/封闭原则”。
三、使用简单工厂模式重构
现在,让我们引入“咖啡店店员”这个角色——一个专门负责创建用户的工厂。
简单工厂模式 (Simple Factory Pattern) 通常由一个单独的函数或类来实现,它根据传入的参数来决定应该创建哪种产品实例。
javascript
// 还是先定义好我们的“产品”
class User {
constructor(name) {
this.name = name;
this.viewPage = ["首页", "商品列表"];
}
}
class Admin {
constructor(name) {
this.name = name;
this.viewPage = ["首页", "商品列表", "用户管理", "订单管理"];
}
}
// 建立我们的“工厂”
class UserFactory {
createUser(role, name) {
switch (role) {
case "user":
return new User(name);
case "admin":
return new Admin(name);
default:
throw new Error("无效的用户角色");
}
}
}
// =======================================================
// 在页面逻辑中,我们这样使用:
// 1. 先创建一个工厂实例
const factory = new UserFactory();
// 2. render 函数现在变得非常纯粹
function render(role, name) {
// 只管向工厂“下单”,不关心具体怎么做
const user = factory.createUser(role, name);
// 专注于自己的渲染逻辑
console.log(`${user.name} 的权限页面: ${user.viewPage.join(", ")}`);
}
render("admin", "张三");
render("user", "李四");
// 如果要新增 SuperAdmin 角色,我们只需要:
// 1. 新建一个 SuperAdmin 类
class SuperAdmin {
/* ... */
}
// 2. 修改工厂,增加一个 case
// class UserFactory { ... case 'superAdmin': return new SuperAdmin(name); ... }
// 注意:我们的 render 函数完全不需要任何改动!通过重构,render 函数不再关心如何创建 user,它把这个任务完全委托给了 UserFactory。render 函数与 User、Admin 类实现了解耦。
四、优缺点与适用场景
优点
- 分离职责:将对象的创建和使用完全分开,实现了关注点分离。
- 降低耦合:使用者(客户端)只依赖于工厂,而不依赖于任何具体的产品类。
- 易于管理:所有创建逻辑都集中在一个地方,使得代码更易于维护和扩展。
缺点
- 增加复杂性:对于非常简单的对象创建,引入工厂模式会增加额外的类和代码,显得有点“小题大做”。
- 工厂职责过重:当产品类型非常多时,工厂类的
switch或if...else会变得非常臃肿,不易维护。(这个问题可以通过更高级的工厂模式,如“工厂方法模式”来解决)。
适用场景
- 当你需要根据不同的参数创建不同类型的对象时。
- 当对象的创建过程比较复杂,不希望将这些复杂性暴露给使用者时。
- 当你希望代码的使用者只关心产品的接口,而不关心其具体实现时。
五、总结
工厂模式就像一个专门负责生产的部门,它把“如何造”的细节隐藏起来,你只需要告诉它“你要什么”,它就能给你对应的产品。这是软件设计中封装变化、降低耦合思想的绝佳体现。