JavaScript - 设计模式
在 JavaScript 中,设计模式是指用于解决特定环境下的一般设计问题的类和通信对象。针对软件设计和开发过程中出现的典型问题,提供通用且可重用的解决方案,被称为软件设计模式。
它们为开发者提供了一个讨论成功设计理念的平台,并作为解决特定类型问题的最佳实践。
什么是设计模式?
软件工程中的设计模式是针对程序设计中典型问题的通用且可复制的解决方案。设计尚未完善,无法立即进行编码。它是一种可用于各种场景的问题解决描述或模型。
软件设计模式的类型
在 JavaScript 中,主要有三类设计模式,分别是:-
- 创建型设计模式
- 结构型设计模式
- 行为型设计模式
让我们逐一讨论一下 -
JavaScript 中的创建型设计模式
在软件开发中,创建型设计模式是设计模式的一个子集。它们作用于对象生成过程,使其更加灵活高效。它维护了系统及其对象的组成、表示和创建的独立性。
单例模式
单例设计模式确保一个类只有一个不可变的实例。简单来说,单例模式是指一个对象无法被更改或复制。当我们希望应用程序拥有一个不可变的单一真实数据点时,它通常很有用。
假设我们想要一个包含应用程序所有配置的对象。我们还希望禁止复制或修改该对象。
实现此模式的两种方法是使用类和对象字面量 -
const Settings = {
initialize: () => console.log('Application is now running'),
refresh: () => console.log('Application data has been refreshed'),
}
// 冻结对象以防止修改
Object.freeze(Settings)
// "应用程序正在运行"
Settings.initialize()
// "应用程序数据已刷新"
Settings.refresh()
// 尝试添加新键
Settings.version = "1.0"
console.log(Settings)
这将生成以下结果 -
Application is now running
Application data has been refreshed
{ initialize: [Function: initialize], refresh: [Function: refresh] }
class Settings {
constructor() {}
launch() { console.log('Application is now running') }
refresh() { console.log('Application data has been refreshed') }
}
const appInstance = new Settings()
Object.freeze(appInstance)
// "应用程序正在运行"
appInstance.launch()
// "应用程序数据已刷新"
appInstance.refresh()
输出
以上代码的结果如下 -
Application is now running Application data has been refreshed
工厂方法模式
工厂方法模式提供了一个用于创建对象的接口,这些对象在生成后可以进行修改。通过将构建对象的逻辑集中到一个地方,这可以简化并增强代码的组织结构。
这种常用的模式可以通过两种方式实现:使用类或工厂函数(即返回对象的方法)。
class Creature {
constructor(name, message) {
this.name = name
this.message = message
this.type = "creature"
}
fly = () => console.log("Whoooosh!!")
speak = () => console.log(this.message)
}
const creature1 = new Creature("Zee", "Hello! I'm Zee from outer space!")
console.log(creature1.name) // output: "Zee"
这将产生以下输出 -
Zee
抽象工厂模式
无需了解链接对象的确切类型,我们可以使用抽象工厂模式来生成一组对象。当我们需要构建仅具有少数共同特征的独特对象时,这种方法非常有用。
它的工作原理如下:一个主抽象工厂连接客户端或用户。然后,该抽象工厂使用某种逻辑调用特定工厂来生成实际对象。因此,它充当了典型工厂布局的覆盖层,使我们能够在一个主工厂中生产各种产品。
例如,我们可以为一家汽车制造商开发一个包含卡车、摩托车和轿车的系统。抽象工厂模式可以通过一个主工厂处理多种对象类型,从而让我们能够轻松地做到这一点。
// 每个类代表一种特定类型的车辆
class Car {
constructor () {
this.type = "Car"
this.wheels = 4
}
startEngine = () => console.log("Vroom Vroom!")
}
class Truck {
constructor () {
this.type = "Truck"
this.wheels = 8
}
startEngine = () => console.log("Rumble Rumble!")
}
class Motorcycle {
constructor () {
this.type = "Motorcycle"
this.wheels = 2
}
startEngine = () => console.log("Zoom Zoom!")
}
const vehicleFactory = {
createVehicle: function (vehicleType) {
switch (vehicleType) {
case "car":
return new Car()
case "truck":
return new Truck()
case "motorcycle":
return new Motorcycle()
default:
return null
}
}
}
const car = vehicleFactory.createVehicle("car")
const truck = vehicleFactory.createVehicle("truck")
const motorcycle = vehicleFactory.createVehicle("motorcycle")
console.log(car.type)
car.startEngine()
console.log(truck.type)
truck.startEngine()
console.log(motorcycle.type)
motorcycle.startEngine()
输出
这将生成以下结果 -
Car Vroom Vroom! Truck Rumble Rumble! Motorcycle Zoom Zoom!
Builder 建造者模式
建造者模式用于分步生成对象。用于添加特定属性或方法的函数或方法通常包含在对象中。
由于它将方法和属性的开发分离为单独的实体,这种设计非常巧妙。
我们创建的对象始终包含由类或工厂函数定义的所有属性和方法。建造者模式使我们能够构造对象并仅执行所需的"步骤",从而提供了一种更灵活的方法。
// 定义我们的生物
const creature1 = {
name: "Buzz Lightwing",
message: "You'll never debug me!"
}
const creature2 = {
name: "Sneaky Bugsworth",
message: "Can't catch me! Ha ha!"
}
// 这些函数为对象添加能力
const enableFlying = obj => {
obj.fly = () => console.log(`${obj.name} has taken flight!`)
}
const enableSpeaking = obj => {
obj.speak = () => console.log(`${obj.name} says: "${obj.message}"`)
}
// 为生物添加能力
enableFlying(creature1)
creature1.fly()
enableSpeaking(creature2)
creature2.speak()
输出
这将给出以下结果 -
Buzz Lightwing has taken flight! Sneaky Bugsworth says: "Can't catch me! Ha ha!"
Prototype 原型模式
通过使用另一个对象作为蓝图并继承其属性和方法,您可以使用原型模式创建对象。
如果您使用过 JavaScript,那么您可能对原型继承及其处理方式很熟悉。
由于方法和属性可以在对象之间共享,而无需依赖于同一个类,因此最终结果比使用类时更加灵活。
// 定义原型对象
const creatureAbilities = {
attack: () => console.log("Zap! Zap!"),
escape: () => console.log("Soaring through the sky!")
}
// 创建新生物
const creature1 = {
name: "Winged Buzz",
message: "You can't debug me!"
}
// 设置原型
Object.setPrototypeOf(creature1, creatureAbilities)
// 确认原型设置成功
console.log(Object.getPrototypeOf(creature1))
console.log(creature1.message)
console.log(creature1.attack())
console.log(creature1.escape())
输出
这将产生以下结果 -
{ attack: [Function: attack], escape: [Function: escape] }
You can't debug me!
Zap! Zap!
undefined
Soaring through the sky!
undefined
JavaScript 中的结构化设计模式
在软件开发中,结构化设计模式是设计模式的一个子集,它关注如何将类或对象组合在一起以创建更复杂、更大的结构。为了提高软件系统的灵活性、可重用性和可维护性,它们有助于组织和管理对象之间的关系。
适配器模式
适配器方法是一种结构化设计模式,它可以帮助您连接两个不兼容接口之间的差距,并使它们协同工作。
让我们看下面的示例 -
// 人口以百万为单位的城市数组
const citiesPopulationInMillions = [
{ city: "Mumbai", population: 12.5 },
{ city: "Delhi", population: 19.0 },
{ city: "Bangalore", population: 8.4 },
{ city: "Kolkata", population: 4.5 },
]
// 我们要添加的新城市
const Chennai = {
city: "Chennai",
population: 7000000
}
const toMillionsAdapter = city => { city.population = parseFloat((city.population / 1000000).toFixed(1)) }
toMillionsAdapter(Chennai)
// 我们将新城市添加到数组中
citiesPopulationInMillions.push(Chennai)
// 此函数返回最大人口数
const CityWithMostPopulation = () => {
return Math.max(...citiesPopulationInMillions.map(city => city.population))
}
console.log(CityWithMostPopulation())
输出
这将生成以下结果 -
19
Decorator 装饰器模式
装饰器方法是一种结构化设计模式,它允许向特定对象添加静态或动态功能,而无需改变同一类中其他对象的行为。
以下是此反应性模式的简单演示 -
// 汽车的基类
class Car {
constructor() {
this.description = "Basic Car";
}
getDescription() {
return this.description;
}
cost() {
// Base price
return 10000;
}
}
// 装饰器添加空调
class AirConditioningDecorator {
constructor(car) {
this.car = car;
}
getDescription() {
return this.car.getDescription() + " + Air Conditioning";
}
cost() {
// 增加空调成本
return this.car.cost() + 1500;
}
}
// 装饰器添加天窗
class SunroofDecorator {
constructor(car) {
this.car = car;
}
getDescription() {
return this.car.getDescription() + " + Sunroof";
}
cost() {
return this.car.cost() + 2000;
}
}
// 创建一辆基本汽车
const myCar = new Car();
console.log(myCar.getDescription());
console.log(`Cost: $${myCar.cost()}`);
// 添加空调功能
const myCarWithAC = new AirConditioningDecorator(myCar);
console.log(myCarWithAC.getDescription());
console.log(`Cost: $${myCarWithAC.cost()}`);
// 为汽车添加天窗功能
const myCarWithACAndSunroof = new SunroofDecorator(myCarWithAC);
console.log(myCarWithACAndSunroof.getDescription());
console.log(`Cost: $${myCarWithACAndSunroof.cost()}`);
输出
这将生成以下结果 -
Basic Car Cost: $10000 Basic Car + Air Conditioning Cost: $11500 Basic Car + Air Conditioning + Sunroof Cost: $13500
Facade 外观模式
外观方法是一种结构化设计模式,它通过为子系统中的一组接口提供更高级别、更简化的接口来改进客户端与子系统的交互。
为了更轻松地与包含汽车中多个复杂部件的子系统进行交互,我们需要创建一个 CarFacade 类。客户端应该只使用 CarFacade 类,而不应该使用单个组件。
// 子系统类
class Engine {
start() {
console.log("Engine started!");
}
stop() {
console.log("Engine stopped!");
}
}
class Transmission {
shiftGear() {
console.log("Transmission shifted!");
}
}
class AirConditioning {
turnOn() {
console.log("Air conditioning is ON.");
}
turnOff() {
console.log("Air conditioning is OFF.");
}
}
// Facade class
class CarFacade {
constructor() {
this.engine = new Engine();
this.transmission = new Transmission();
this.airConditioning = new AirConditioning();
}
startCar() {
console.log("Starting the car...");
this.engine.start();
this.transmission.shiftGear();
this.airConditioning.turnOn();
}
stopCar() {
console.log("Stopping the car...");
this.airConditioning.turnOff();
this.transmission.shiftGear();
this.engine.stop();
}
}
// 使用 Facade 模式的客户端代码
const myCar = new CarFacade();
// 启动汽车
myCar.startCar();
// 停止汽车
myCar.stopCar();
输出
这将生成以下结果 -
Starting the car... Engine started! Transmission shifted! Air conditioning is ON. Stopping the car... Air conditioning is OFF. Transmission shifted! Engine stopped!
Proxy 代理模式
代理方法是一种结构化设计模式,它允许您创建一个对象替代品,该替代品可以控制对真实对象的访问,或充当中间件。
// 真实对象
class RealImage {
constructor(filename) {
this.filename = filename;
this.load();
}
load() {
console.log(`Loading image: ${this.filename}`);
}
display() {
console.log(`Displaying image: ${this.filename}`);
}
}
// Proxy class
class ProxyImage {
constructor(filename) {
this.filename = filename;
// 仅在需要时加载真实图像
this.realImage = null;
}
load() {
if (!this.realImage) {
// 延迟加载
this.realImage = new RealImage(this.filename);
}
}
display() {
// 确保在显示之前已加载真实图像
this.load();
this.realImage.display();
}
}
// 客户端代码
const image1 = new ProxyImage("image1.jpg");
const image2 = new ProxyImage("image2.jpg");
// 图片尚未加载
image1.display();
// 图片已加载
image1.display();
// 已加载另一张图片
image2.display();
输出
这将导致以下结果 -
Loading image: image1.jpg Displaying image: image1.jpg Displaying image: image1.jpg Loading image: image2.jpg Displaying image: image2.jpg
JavaScript 中的行为设计模式
行为设计模式是软件开发设计模式的一个子集,专注于类和对象之间的交互和通信。它们关注类和对象如何协同工作并通信以履行职责。
责任链模式
责任链用于通过多个处理程序路由请求。每个处理程序决定是处理请求还是将其传递给链中的下一个处理程序。
我们可以使用与之前相同的示例来演示此模式,因为 Express 中的中间件就是处理程序,它可以执行请求,也可以将其转发给下一个处理程序。
迭代器模式
迭代器可用于遍历集合中的元素。这在当今的计算机语言中似乎并不重要,但过去却并非如此。
任何允许我们在数据结构之间进行迭代的内置 JavaScript 函数(for、forEach、for...of、for...in、map、reduce、filter 等等)都可以使用迭代器模式。
与任何遍历技术一样,我们编写代码来迭代具有高级结构(例如树或图)的数据。
观察者模式
观察者模式允许您创建一种订阅机制,该机制会将所观察项目的任何更改通知给多个项目。基本上,该规则就像在特定对象上设置一个事件监听器,当该对象执行我们期望的操作时,我们就会执行某些操作。
React 的 useEffect hook 就是一个很好的例子。 UseEffect 会在声明时执行提供的函数。
该钩子分为两部分:可执行函数和依赖项数组。如果数组为空(如下例所示),则每次显示组件时都会调用该函数。
useEffect(() => { console.log('组件已渲染') }, [])
如果我们在依赖项数组中声明任何变量,则该函数仅在这些变量发生变化时运行。
useEffect(() => { console.log('var1 已更改') }, [var1])
即使是简单的 JavaScript 事件监听器也可以被视为观察者。反应式编程和 RxJS 等工具也说明了这种方法,它们可以跨系统管理异步信息和事件。

