前言
从这一篇开始,我会持续地更新每一种设计模式的内容,争取用通俗易懂的语言讲解和解释清楚。如果对你学习设计模式有帮助,请不要吝啬手中的赞~ 如果对文章内容有任何疑惑都可以在评论区提出和讨论~
本系列文章中的完整源码已上传 github 仓库,你可以在这里 FatMii/Design-Mode 获取。同样的,如果对你有帮助,请给我一个star~谢谢
设计模式合集链接:
谈到设计模式,许多软件开发者,尤其是刚入行的前端开发人员
,往往会将其视为一种难以攀登的高峰,心中充满了畏惧,不太愿意投入时间和精力去学习。在我与身边前端开发者交流中,归纳出他们对学习设计模式抗拒的主要原因有两点:
-
枯燥乏味:设计模式的学习通常不像掌握某个编程框架的API那样直接与日常开发任务相关联。它们通常是抽象的、理论性较强的概念,没有即时的、可见的成果,这对于喜欢看到快速成果的开发者来说可能显得比较枯燥。
-
实际应用不频繁:许多开发者认为,在日常的业务代码编写中很少有机会实际应用到设计模式。这种观点源自于设计模式通常在解决更复杂的架构问题或者需要高度可重用代码的场景中才显得尤为重要,而这些场合在平时的业务开发中可能不那么常见。
虽然存在这些障碍,但是不管前端还是后端开发人员,设计模式的学习和理解对我们的长远成长至关重要。设计模式不仅仅是提供了一套解决问题的模板,更重要的是,它们培养了开发者系统化思考问题的能力
,提高了对软件设计的敏感性和深度。这些模式帮助开发者构建更加健壮、灵活且可维护的代码,特别是在项目规模扩大和团队协作的环境下显得尤为重要。
工厂模式:
什么是工厂模式?我们先抛开复杂的定义,简单想象一下我们日常生活中的工厂。工厂的基本功能是生产物品
。具体生产什么物品则取决于"客户"的需求。例如,如果客户需要凳子,工厂便生产凳子;如果需要餐桌,工厂则生产餐桌。总之,工厂模式的操作就是这样直接而简单。
详细来说,工厂模式通常分为三个子类。在接下来的部分中,我们将介绍并通过代码示例展示这三种类型。请注意,无论采用哪种子类,工厂模式的核心目标始终是"生产物品"
。只需掌握这一核心思想即可。
一、简单工厂模式
简单工厂模式是一种创建型设计模式,它用于在不指定具体类的情况下创建对象
的接口。简单工厂模式可以帮助我们将对象的创建与其使用分离,使得程序在不同情况下可以选择不同的对象进行实例化
,而这一切的选择逻辑都被封装在一个单独的工厂类
中。
假设我们需要创建不同类型的员工对象,如"全职员工"
、"兼职员工"
和"临时员工"
。每种类型的员工都有不同的薪资计算方式。
步骤1: 定义员工构造函数
首先,我们定义几个构造函数,每个构造函数代表一种类型的员工。
javascript
class FullTime {
constructor() {
this.hourly = "$12";
}
}
class PartTime {
constructor() {
this.hourly = "$11";
}
}
class Temporary {
constructor() {
this.hourly = "$10";
}
}
class Contractor {
constructor() {
this.hourly = "$15";
}
}
步骤2:创建工厂来产生员工
然后,我们创建一个工厂来决定基于给定的类型返回哪种员工对象
javascript
class EmployeeFactory {
create(type) {
switch (type) {
case "fulltime":
return new FullTime();
case "parttime":
return new PartTime();
case "temporary":
return new Temporary();
case "contractor":
return new Contractor();
default:
return null;
}
}
}
步骤3: 使用工厂
现在,我们可以使用工厂来创建任何类型的员工,而无需直接实例化具体类。
javascript
const factory = new EmployeeFactory();
// Create employee instances
const employees = [];
employees.push(factory.create("fulltime"));
employees.push(factory.create("parttime"));
employees.push(factory.create("temporary"));
employees.push(factory.create("contractor"));
employees.forEach(emp => console.log(emp));
// FullTime { hourly: '$12' }
// PartTime { hourly: '$11' }
// Temporary { hourly: '$10' }
// Contractor { hourly: '$15' }
简单工厂模式的缺点
-
简单工厂模式缺点在于不符合"开闭原则",举个例子,如果要每次添加一种新类型的员工是不是就需要修改工厂类
EmployeeFactory
,在create
方法中的switch
里面再加入一个分支判断。 -
在员工类型类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,并且工厂类集中了所有类型员工的创建逻辑,一旦不能正常工作,整个系统都要受到影响
二、工厂方法模式
工厂方法模式的特点就是:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
这种模式的适用场景是当对象的具体类型未知或在将来可能改变。
还是生产员工的例子,我们来看一下具体代码是如何实现的。
步骤1: 定义员工类
javascript
// 定义员工基类
class Employee {
constructor(hourly) {
this.hourly = hourly;
}
}
// 具体员工类
class FullTime extends Employee {
constructor() {
super("$12");
}
}
class PartTime extends Employee {
constructor() {
super("$11");
}
}
class Temporary extends Employee {
constructor() {
super("$10");
}
}
class Contractor extends Employee {
constructor() {
super("$15");
}
}
步骤2:为每一种员工类型定义工厂类
javascript
// 抽象工厂类
class EmployeeFactory {
constructor() {
this.createdCount = 0;
}
increaseCount() {
this.createdCount++;
console.log(
`A new employee has been created. Total created: ${this.createdCount}`
);
}
}
// 具体工厂类
class FullTimeFactory extends EmployeeFactory {
createEmployee() {
this.increaseCount();
return new FullTime();
}
}
class PartTimeFactory extends EmployeeFactory {
createEmployee() {
this.increaseCount();
return new PartTime();
}
}
class TemporaryFactory extends EmployeeFactory {
createEmployee() {
this.increaseCount();
return new Temporary();
}
}
class ContractorFactory extends EmployeeFactory {
createEmployee() {
this.increaseCount();
return new Contractor();
}
}
步骤3: 使用
javascript
// 创建具体工厂实例
const fullTimeFactory = new FullTimeFactory();
const partTimeFactory = new PartTimeFactory();
const temporaryFactory = new TemporaryFactory();
const contractorFactory = new ContractorFactory();
// 使用工厂创建员工对象
const employees = [];
employees.push(fullTimeFactory.createEmployee());
employees.push(partTimeFactory.createEmployee());
employees.push(temporaryFactory.createEmployee());
employees.push(contractorFactory.createEmployee());
// 输出创建的员工信息
employees.forEach(emp => console.log(emp));
A new employee has been created. Total created: 1
A new employee has been created. Total created: 1
A new employee has been created. Total created: 1
A new employee has been created. Total created: 1
// FullTime { hourly: '$12' }
// PartTime { hourly: '$11' }
// Temporary { hourly: '$10' }
// Contractor { hourly: '$15' }
优点
-
从代码中可以看到,工厂方法模式的核心特点在于将
"创建员工实例"
的职责转移到了各自的子类
中,从而将控制权回归到具体的工厂子类手中。这种设计显著降低了与EmployeeFactory
基类的耦合性,增强了系统的灵活性和扩展性。 -
若未来需要添加新类型的员工,如类型A,我们只需简单地新增一个相应的
A
类及其对应的AFactory
类即可。这样的设计避免了对现有工厂基类的任何修改,确保了代码的开放封闭原则得以实现,有利于维护和升级。
缺点
- 类的数量增多:
- 每增加一种新的员工类型,都需要增加一个新的具体工厂类。这在一定程度上增加了系统的复杂性,因为随着员工类别的增多,需要管理的类和对象也相应增加
- 代码复杂度增加:
- 如果有多种类似的产品需要创建,工厂方法可能会导致代码中存在大量的工厂类,每个工厂类只有很少的差别。这可能会使得系统更难以理解和维护。
三、抽象工厂模式
-
上面介绍的
简单工厂模式
和工厂方法模式
都是直接生成实例,但是抽象工厂模式不同,抽象工厂模式并不直接生成实例, 而是用于对员工类簇的创建 -
这次我们用另外一个案例来演示:为用户生产
不同类型的用户界面组件
,比如按钮和对话框。这些组件将根据操作系统的不同有不同的样式和行为。这里,我们假设有两种风格的组件集合:一种是为 Windows 系统设计的,另一种是为 MacOS 系统设计的。
代码案例
javascript
// 抽象类 UIComponentFactory,使用错误抛出来模拟抽象方法
class UIComponentFactory {
createButton() {
throw new Error("This method should be implemented by subclasses");
}
createDialog() {
throw new Error("This method should be implemented by subclasses");
}
}
// 具体工厂 WindowsFactory
class WindowsFactory extends UIComponentFactory {
createButton() {
return new WindowsButton();
}
createDialog() {
return new WindowsDialog();
}
}
// 具体工厂 MacOSFactory
class MacOSFactory extends UIComponentFactory {
createButton() {
return new MacOSButton();
}
createDialog() {
return new MacOSDialog();
}
}
// 抽象产品类 Button
class Button {
click() {
throw new Error("This method should be implemented by subclasses");
}
}
// 具体产品 WindowsButton
class WindowsButton extends Button {
click() {
console.log("You clicked a Windows style button!");
}
}
// 具体产品 MacOSButton
class MacOSButton extends Button {
click() {
console.log("You clicked a MacOS style button!");
}
}
// 抽象产品类 Dialog
class Dialog {
open() {
throw new Error("This method should be implemented by subclasses");
}
}
// 具体产品 WindowsDialog
class WindowsDialog extends Dialog {
open() {
console.log("Opening a Windows style dialog!");
}
}
// 具体产品 MacOSDialog
class MacOSDialog extends Dialog {
open() {
console.log("Opening a MacOS style dialog!");
}
}
// 使用抽象工厂
function application(factory) {
const button = factory.createButton();
const dialog = factory.createDialog();
button.click();
dialog.open();
}
// 创建具体的工厂实例
const windowsFactory = new WindowsFactory();
const macosFactory = new MacOSFactory();
// 根据当前平台运行应用程序
console.log("Test Windows UI:");
application(windowsFactory);
console.log("Test MacOS UI:");
application(macosFactory);
优点
- 可维护性和扩展性 :抽象工厂模式允许你轻松添加新的具体工厂和产品类而不影响现有的客户端代码。例如,如果需要支持另一种操作系统的用户界面风格,例如 Linux,你只需创建一个新的
具体工厂
和一系列产品
实现即可。 - 封装性 :每个工厂封装了创建具体产品的过程。客户端代码只与
抽象产品
和工厂接口
打交道,而不直接与具体产品类
交互。这降低了系统各部分之间的依赖关系,有助于减少系统的复杂性。 - 解耦:工厂接口和产品接口的使用解耦了产品的创建和使用。客户端代码基于接口编程,而不是基于实现编程,这使得更改一个组件的实现不会影响到使用该组件的代码。
- 一致性:使用抽象工厂模式可以确保一致地创建相互关联或依赖的对象集合。比如,在用户界面设计中,确保同一操作系统下的所有组件(如按钮、对话框)都具有一致的风格。
- 替代方案的灵活性:可以根据运行时条件动态选择使用哪个具体工厂,如示例中根据不同操作系统选择不同的 UI 组件集。这为动态更改应用程序的行为提供了很好的支持。