JavaScript 中的设计模式:综合指南
JavaScript 凭借其广泛的应用和多功能性,已成为现代 Web 开发的基石。随着你对 JavaScript 开发的深入,理解和运用模式变得至关重要。在本文中,我们将揭开 JavaScript 模式的神秘面纱,并探索它们如何提升你的编码实践。
先决条件
要理解本文讨论的概念和技术,你需要了解 JavaScript 的基础知识。熟悉变量、函数、数据类型、面向对象编程等概念至关重要。
在继续之前,让我们花点时间来了解 JavaScript 作为一种编程语言的重要性。
JavaScript 作为编程语言
JavaScript,通常被称为“Web 语言”,是一种动态的高级编程语言。它主要用于 Web 浏览器的客户端脚本编写,但随着 Node.js 的出现,它在服务器端也获得了广泛的应用。JavaScript 的主要功能包括操作 DOM、处理事件、为网页提供交互性等。
话虽如此,让我们简要讨论一下 JavaScript 中模式的重要性和目的。
模式在 JavaScript 开发中的重要性
JavaScript 中的模式是解决软件开发过程中反复遇到的问题的有效解决方案。它们提供结构,改进代码组织,增强可维护性并提高可重用性。通过理解和应用模式,开发人员可以编写更简洁、更高效的代码,并有效地应对复杂的挑战。
理解 JavaScript 模式的目的
理解 JavaScript 模式不仅仅是记住语法或遵循最佳实践。它使开发人员能够批判性地思考软件设计,选择合适的解决方案,并构建可扩展的应用程序。通过掌握 JavaScript 模式,您可以深入了解该语言及其生态系统,从而编写出健壮且易于维护的代码。
现在我们知道了 JavaScript 模式的重要性和目的,让我们深入研究 JS 设计模式的基础知识。
设计模式基础
在本节中,我们为理解 JavaScript 开发环境中的设计模式奠定基础。
设计模式的定义和特点
设计模式是可复用的模板,它囊括了解决反复出现的软件设计问题的最佳实践。它们提供了一种结构化的软件系统设计方法,并促进了模块化、灵活且易于维护的代码。设计模式的共同特征包括其目的、结构、参与者和协作。
设计模式的类型
设计模式可以分为三种主要类型:
-
创造型
-
结构
-
行为
了解这些类别有助于确定针对特定问题的适当模式。
- 创建模式
创建模式专注于对象创建机制,提供以灵活且可控的方式实例化对象的方法。JavaScript 中一些常用的创建模式包括:
-
单例模式
-
工厂
-
构造函数
-
原型
-
建造者
-
模块
单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。当你想要限制类的实例数量,并确保在整个应用程序中只能访问一个共享实例时,此模式非常有用。
// Implementation example of the Singleton Pattern
class Singleton {
constructor() {
if (!Singleton.instance) {
// Initialize the instance
Singleton.instance = this;
}
return Singleton.instance;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // Output: true
在此示例中,Singleton 类有一个构造函数,用于检查该类的实例是否已存在。如果实例不存在(!Singleton.instance
条件),它会通过将 this 赋值给 来初始化该实例Singleton.instance
。这确保了后续调用构造函数将返回同一个实例。
当使用 new Singleton() 语法创建实例1和实例2时,两个变量都指向同一个 Singleton 类实例。因此,当使用严格相等运算符比较实例1 === 实例2 时,结果为 true。
工厂模式
工厂模式提供了一种无需指定具体类即可创建对象的方法。它将对象创建逻辑封装在单独的工厂方法中,从而实现了灵活性,并实现了创建者和被创建对象之间的解耦。
// Implementation example of the Factory Pattern
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
}
class CarFactory {
createCar(make, model) {
return new Car(make, model);
}
}
const factory = new CarFactory();
const myCar = factory.createCar("Tope", "Model 1");
在此示例中,CarFactory
使用 创建了一个实例new CarFactory()
,然后createCar
在工厂中使用参数“Tope”和“Model 1”调用该方法。这将创建一个新的 Car 对象,其品牌为“Tope”,型号为“Model 1”,并将其赋值给myCar
变量。
构造函数模式
构造函数模式使用关键字从构造函数创建对象new
。它允许你在构造函数中定义和初始化对象属性。
// Implementation example of the Constructor Pattern
function Person(name, age) {
this.name = name;
this.age = age;
}
const tope = new Person("Tope", 24);
上面的代码定义了一个名为 Person 的构造函数,它接受两个参数:name 和 age。在函数内部,使用 this 关键字将 name 和 age 的值赋值给新创建的对象的各个属性。
稍后,通过调用 Person 函数并传入参数“Tope”和 24,创建 Person 对象的新实例。这将创建一个新对象,其 name 属性设置为“Tope”,age 属性设置为 24,然后将其赋值给变量 tope。这段代码的输出是 Tope 保存了一个对象,该对象代表一个名字为“Tope”、年龄为 24 的人。
原型模式
JavaScript 中的原型模式专注于通过克隆或扩展现有对象作为原型来创建对象。它允许我们创建新实例,而无需显式定义其类。在此模式中,对象充当创建新对象的原型,从而实现继承以及在多个对象之间共享属性和方法。
// Prototype object
const carPrototype = {
wheels: 4,
startEngine() {
console.log("Engine started.");
},
stopEngine() {
console.log("Engine stopped.");
}
};
// Create new car instance using the prototype
const car1 = Object.create(carPrototype);
car1.make = "Toyota";
car1.model = "Camry";
// Create another car instance using the same prototype
const car2 = Object.create(carPrototype);
car2.make = "Honda";
car2.model = "Accord";
car1.startEngine(); // Output: "Engine started."
car2.stopEngine(); // Output: "Engine stopped."
在此示例中,使用原型对象 carPrototype 创建了汽车实例 car1 和 car2。car1 的品牌为“Toyota”,型号为“Camry”,而 car2 的品牌为“Honda”,型号为“Accord”。car1.startEngine()
调用时,它会输出“Engine started.”;car2.stopEngine()
调用时,它会输出“Engine stopped.”。这演示了如何利用原型对象在多个实例之间共享属性和方法。
建造者模式
在建造者模式中,建造者类或对象负责构建最终对象。它提供了一组方法来配置和设置正在构建对象的属性。构建过程通常涉及按特定顺序调用这些方法来逐步构建对象。
class CarBuilder {
constructor() {
this.car = new Car();
}
setMake(make) {
this.car.make = make;
return this;
}
setModel(model) {
this.car.model = model;
return this;
}
setEngine(engine) {
this.car.engine = engine;
return this;
}
setWheels(wheels) {
this.car.wheels = wheels;
return this;
}
build() {
return this.car;
}
}
class Car {
constructor() {
this.make = "";
this.model = "";
this.engine = "";
this.wheels = 0;
}
displayInfo() {
console.log(`Make: ${this.make}, Model: ${this.model}, Engine: ${this.engine}, Wheels: ${this.wheels}`);
}
}
// Usage
const carBuilder = new CarBuilder();
const car = carBuilder.setMake("Toyota").setModel("Camry").setEngine("V6").setWheels(4).build();
car.displayInfo(); // Output: Make: Toyota, Model: Camry, Engine: V6, Wheels: 4
在此示例中,该类CarBuilder
允许构建具有不同属性的 Car 对象。通过调用setMake
、setModel
、setEngine
、setWheels
方法,可以设置 Car 对象的属性。build 方法完成构建并返回完全构建的 Car 对象。Car 类表示一辆汽车,并包含一个用于displayInfo
记录其详细信息的方法。通过创建carBuilder
实例并链接属性设置方法,可以构建一个具有特定品牌、型号、引擎和车轮值的汽车对象。调用 将car.displayInfo()
显示汽车的信息。
模块模式
模块模式将相关方法和属性封装到单个模块中,从而提供一种简洁的方式来组织和保护代码。它允许私有和公共成员,从而实现信息隐藏并防止全局命名空间污染。
const MyModule = (function() {
// Private members
let privateVariable = "I am private";
function privateMethod() {
console.log("This is a private method");
}
// Public members
return {
publicVariable: "I am public",
publicMethod() {
console.log("This is a public method");
// Accessing private members within the module
console.log(privateVariable);
privateMethod();
}
};
})();
// Usage
console.log(MyModule.publicVariable); // Output: "I am public"
MyModule.publicMethod(); // Output: "This is a public method" "I am private" "This is a private method"
在此示例中,代码使用立即调用函数表达式(IIFE)来封装私有成员和公共成员。该模块包含私有变量和方法,以及公共变量和方法。访问公共成员时,它们会提供预期的输出。此模式允许对封装的私有成员进行受控访问,同时公开选定的公共成员。
- 结构模式
结构模式专注于组织和组合对象以形成更大的结构。它们有助于对象的组合,定义对象之间的关系,并提供灵活的方法来操作它们的结构。JavaScript 中一些常用的结构模式包括:
-
装饰器模式
-
外观模式
-
适配器
-
桥
-
合成的
装饰器模式
装饰器模式允许你动态地添加行为或修改对象的现有行为。它通过用一个或多个装饰器包装对象来增强对象的功能,而无需修改其结构。
// Implementation example of the Decorator Pattern
class Coffee {
getCost() {
return 1;
}
}
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
}
getCost() {
return this.coffee.getCost() + 0.5;
}
}
const myCoffee = new Coffee();
const coffeeWithMilk = new CoffeeDecorator(myCoffee);
console.log(coffeeWithMilk.getCost()); // Output: 1.5
在此示例中,该类CoffeeDecorator
包装了一个基础Coffee
对象并添加了附加功能。它有一个getCost
方法,通过将基础咖啡的价格与 0.5 的额外价格相加来计算总价格。
在使用部分,会创建myCoffee
该类的一个实例。然后,实例化该类的一个实例,并将其作为参数传递。调用时,它会返回咖啡的总价格加上装饰器添加的价格,最终输出结果为 1.5。此示例说明了装饰器模式如何通过动态添加或修改对象的属性或方法来扩展对象的功能。Coffee
coffeeWithMilk
CoffeeDecorator
myCoffee
coffeeWithMilk.getCost()
外观模式
外观模式为复杂的子系统提供了一个简化的接口,充当隐藏底层实现细节的前端接口。它通过提供高级接口,提供了一种与复杂系统交互的便捷方式。
// Implementation example of the Facade Pattern
class SubsystemA {
operationA() {
console.log("Subsystem A operation.");
}
}
class SubsystemB {
operationB() {
console.log("Subsystem B operation.");
}
}
class Facade {
constructor() {
this.subsystemA = new SubsystemA();
this.subsystemB = new SubsystemB();
}
operation() {
this.subsystemA.operationA();
this.subsystemB.operationB();
}
}
const facade = new Facade();
facade.operation(); // Output: "Subsystem A operation." "Subsystem B operation."
在此示例中,代码由三个类组成:SubsystemA
、SubsystemB
和Facade
。SubsystemA
和SubsystemB
类代表独立的子系统,并具有各自的operationA
和operationB
方法。Facade
类用作简化的接口,用于聚合子系统的功能。
在用法部分,创建了facade
该类的一个实例。调用会触发from和from的执行。结果,输出显示“子系统 A 操作。”,然后是“子系统 B 操作。”。这演示了外观模式如何提供统一且简化的接口来与复杂的子系统交互,从而抽象它们的复杂性并使其更易于使用。Facade
facade.operation()
operationA
SubsystemA
operationB
SubsystemB
适配器模式
适配器模式是一种结构化设计模式,它充当不兼容接口的对象之间的桥梁,使它们能够协作。它提供了一种将一个对象的接口转换为客户端期望的另一个接口的方法。
// Implementation
class LegacyPrinter {
printLegacy(text) {
console.log(`Legacy Printing: ${text}`);
}
}
// Target interface
class Printer {
print(text) {}
}
// Adapter
class PrinterAdapter extends Printer {
constructor() {
super();
this.legacyPrinter = new LegacyPrinter();
}
print(text) {
this.legacyPrinter.printLegacy(text);
}
}
// Usage
const printer = new PrinterAdapter();
printer.print("Hello, World!"); // Output: "Legacy Printing: Hello, World!"
这段代码中,适配器模式用于弥合LegacyPrinter
类与所需Printer
接口之间的差距。PrinterAdapter
扩展了Printer
类,并在内部利用 来LegacyPrinter
适配print
方法。当printer.print("Hello, World!")
被调用时,它会有效地触发旧版打印功能,并输出“Legacy Printing: Hello, World!”。这展示了适配器模式如何通过提供标准化接口来实现不兼容组件的集成。
桥梁模式
桥接模式是一种结构化设计模式,它将系统的抽象和实现分离,使其能够独立演进。它使用接口或抽象类在两者之间建立桥梁。以下是一段示例代码片段,用于演示桥接模式:
// Example
class Shape {
constructor(color) {
this.color = color;
}
draw() {}
}
// Concrete Abstractions
class Circle extends Shape {
draw() {
console.log(`Drawing a ${this.color} circle`);
}
}
class Square extends Shape {
draw() {
console.log(`Drawing a ${this.color} square`);
}
}
// Implementor
class Color {
getColor() {}
}
// Concrete Implementors
class RedColor extends Color {
getColor() {
return "red";
}
}
class BlueColor extends Color {
getColor() {
return "blue";
}
}
// Usage
const redCircle = new Circle(new RedColor());
redCircle.draw(); // Output: "Drawing a red circle"
const blueSquare = new Square(new BlueColor());
blueSquare.draw(); // Output: "Drawing a blue square"
在这个例子中,我们用 Shape 类来表示抽象,它具有 color 属性和 draw 方法。具体抽象 Circle 和 Square 继承自 Shape 类,并实现了它们各自的 draw 行为。Implementor
Color 类表示 ,它声明了 的getColor
方法。具体抽象Implementors
、RedColor
和BlueColor
继承自 Color 类,并提供各自的颜色实现。
在使用部分,我们创建具体抽象的实例,并传递相应的具体实现对象。这使得抽象可以将与颜色相关的功能委托给实现对象。当我们调用 draw 方法时,它会从实现对象中获取颜色并相应地执行绘制操作。
复合模式
组合模式是一种结构化设计模式,允许你统一处理单个对象和对象组合。它允许你创建层次结构,其中每个元素可以被视为单个对象或对象集合。该模式使用通用接口来表示单个对象(叶节点)和对象组合(复合节点),从而允许客户端以统一的方式与它们交互。
// Implementation
class Employee {
constructor(name) {
this.name = name;
}
print() {
console.log(`Employee: ${this.name}`);
}
}
// Composite
class Manager extends Employee {
constructor(name) {
super(name);
this.employees = [];
}
add(employee) {
this.employees.push(employee);
}
remove(employee) {
const index = this.employees.indexOf(employee);
if (index !== -1) {
this.employees.splice(index, 1);
}
}
print() {
console.log(`Manager: ${this.name}`);
for (const employee of this.employees) {
employee.print();
}
}
}
// Usage
const john = new Employee("John Doe");
const jane = new Employee("Jane Smith");
const mary = new Manager("Mary Johnson");
mary.add(john);
mary.add(jane);
const peter = new Employee("Peter Brown");
const bob = new Manager("Bob Williams");
bob.add(peter);
bob.add(mary);
bob.print();
在这个例子中,我们有一个组件类 Employee,它代表单个员工。复合类 Manager 扩展了 Employee 类,可以包含一个员工集合。它提供了从集合中添加和删除员工的方法,并重写了 print 方法以显示经理的姓名及其下属的员工。
在用法部分,我们创建一个复合层次结构,其中 Manager 对象可以包含单个员工(Employee)和其他经理(Manager)。我们将员工添加到经理,从而构建一个层级结构。最后,我们在顶级经理上调用 print 方法,该方法递归打印层次结构,显示经理及其各自的员工。
- 行为模式
行为模式关注对象之间的交互和职责分配。它们为对象之间的沟通、协调和协作提供了解决方案。以下是行为模式的类型。
-
观察者模式
-
策略模式
-
命令模式
-
迭代器模式
-
中介者模式
观察者模式
观察者模式在对象之间建立一对多的关系,其中多个观察者会收到主体状态变化的通知。它实现了对象之间的松散耦合,并促进了事件驱动的通信。
// Implementation example of the Observer Pattern
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
notifyObservers() {
this.observers.forEach((observer) => observer.update());
}
}
class Observer {
update() {
console.log("Observer is notified of changes.");
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers(); // Output: "Observer is notified of changes." "Observer is notified of changes."
在此示例中,该类Subject
表示一个主体,它维护一个观察者列表,并提供添加、移除和通知观察者的方法。该类Observer
通过其方法定义观察者的行为update
。在用法部分,创建了subject
该类的一个实例。此外,还创建了两个实例,并使用该方法将其添加到主体中。Subject
observer
addObserver
当subject.notifyObservers()
被调用时,它会触发update
每个观察者的方法。结果,输出“Observer is notified of changes.”被打印了两次,表明观察者已经收到了主题变更的通知。
策略模式
策略模式允许你将可互换的算法封装在单独的策略对象中。它支持在运行时动态选择算法,从而提高灵活性和可扩展性。
// Implementation example of the Strategy Pattern
class Context {
constructor(strategy) {
this.strategy = strategy;
}
executeStrategy() {
this.strategy.execute();
}
}
class ConcreteStrategyA {
execute() {
console.log("Strategy A is executed.");
}
}
class ConcreteStrategyB {
execute() {
console.log("Strategy B is executed.");
}
}
const contextA = new Context(new ConcreteStrategyA());
contextA.executeStrategy(); // Output: "Strategy A is executed."
const contextB = new Context(new ConcreteStrategyB());
contextB.executeStrategy(); // Output: "Strategy B is executed."
在此示例中,Context
类表示一个封装了不同策略的上下文,其中包含一个strategy
属性和一个executeStrategy
方法。这里有两个具体的策略类,ConcreteStrategyA
和ConcreteStrategyB
,每个类都有各自的execute
方法来输出特定的消息。
在用法部分,使用作为策略创建类contextA
的实例。调用会触发的方法,输出“策略 A 已执行”。类似地,使用作为策略创建实例,调用会触发的方法,输出“策略 B 已执行”。这演示了策略模式如何通过将行为封装在不同的策略对象中来实现在运行时动态选择行为。Context
ConcreteStrategyA
contextA.executeStrategy()
execute
ConcreteStrategyA
contextB
ConcreteStrategyB
contextB.executeStrategy()
execute
ConcreteStrategyB
命令模式
命令模式将请求封装为对象,允许你使用不同的请求参数化客户端、将请求排队或记录日志,并支持撤消操作。它将请求的发送方与接收方解耦,从而实现松耦合和灵活性。
// Implementation
class Receiver {
execute() {
console.log("Receiver executes the command.");
}
}
class Command {
constructor(receiver) {
this.receiver = receiver;
}
execute() {
this.receiver.execute();
}
}
class Invoker {
setCommand(command) {
this.command = command;
}
executeCommand() {
this.command.execute();
}
}
const receiver = new Receiver();
const command = new Command(receiver);
const invoker = new Invoker();
invoker.setCommand(command);
invoker.executeCommand(); // Output: "Receiver executes the command."
在此示例中,Receiver
类在调用时执行命令,并且该类Command
封装了一个命令并将执行委托给接收者。该类Invoker
设置并执行了一个命令。在用法部分,创建了接收者、命令和调用者。为调用者设置了命令,并且调用invoker.executeCommand()
执行了该命令,从而输出“接收者执行该命令”。
迭代器模式
迭代器模式是一种行为设计模式,它提供了一种顺序访问聚合对象元素的方法,而无需暴露其底层表示。它允许您以统一的方式遍历对象集合,而无需考虑集合的具体实现。该模式将遍历逻辑与集合分离,从而提供了一种简洁灵活的元素迭代方法。
// Implementation
class Collection {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
createIterator() {}
}
// Concrete Aggregate
class ConcreteCollection extends Collection {
createIterator() {
return new ConcreteIterator(this);
}
}
// Iterator
class Iterator {
constructor(collection) {
this.collection = collection;
this.index = 0;
}
hasNext() {}
next() {}
}
// Concrete Iterator
class ConcreteIterator extends Iterator {
hasNext() {
return this.index < this.collection.items.length;
}
next() {
return this.collection.items[this.index++];
}
}
// Usage
const collection = new ConcreteCollection();
collection.addItem("Item 1");
collection.addItem("Item 2");
collection.addItem("Item 3");
const iterator = collection.createIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}
在这段代码中,我们用 Collection 类来表示聚合,它定义了创建迭代器对象的接口。具体聚合则ConcreteCollection
扩展了 Collection 类,并提供了迭代器创建的具体实现。
迭代器由 Iterator 类表示,该类定义了用于访问和遍历元素的接口。具体迭代器 (Concrete Iterator)ConcreteIterator
扩展了 Iterator 类,并提供了迭代逻辑的具体实现。在使用部分,我们创建了具体聚合 (Concrete Aggregate) 的实例ConcreteCollection
,并向其中添加项。然后,我们使用createIterator
方法创建一个迭代器。通过使用迭代器的hasNext
和 next 方法,我们可以迭代集合并打印每个项。
中介者模式
中介者模式通过引入中介者对象来简化对象通信。中介者对象充当协调对象间交互的中心枢纽。它封装了通信逻辑,并为对象提供了注册、发送和接收消息的方法。
// Implementation
class Mediator {
constructor() {
this.colleague1 = null;
this.colleague2 = null;
}
setColleague1(colleague) {
this.colleague1 = colleague;
}
setColleague2(colleague) {
this.colleague2 = colleague;
}
notifyColleague1(message) {
this.colleague1.receive(message);
}
notifyColleague2(message) {
this.colleague2.receive(message);
}
}
class Colleague {
constructor(mediator) {
this.mediator = mediator;
}
send(message) {
// Send a message to the mediator
this.mediator.notifyColleague2(message);
}
receive(message) {
console.log(`Received message: ${message}`);
}
}
// Usage
const mediator = new Mediator();
const colleague1 = new Colleague(mediator);
const colleague2 = new Colleague(mediator);
mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);
colleague1.send("Hello Colleague 2!"); // Output: "Received message: Hello Colleague 2!"
在这个例子中,我们有一个 Mediator 类,它充当两个 Colleague 对象之间的中介。Mediator 持有对 Colleague 对象的引用,并提供在 Colleague 对象之间发送消息的方法。
每个同事对象都引用了中介器,可以通过通知中介器来发送消息。中介器则将消息转发给相应的同事。在本例中,同事 1 向同事 2 发送了一条消息,后者接收并记录了该消息。
结论
我们探索了 JavaScript 中的一系列基本设计模式,包括创建型模式、结构型模式和行为型模式。创建型模式使我们能够以灵活高效的方式创建对象。结构型模式有助于提高程序的灵活性和可扩展性。行为型模式则支持 JavaScript 对象之间的有效通信和交互。通过利用这些设计模式,JavaScript 开发者可以提高代码的可重用性、可维护性和整体系统性能。凭借这些知识,我们可以构建出满足现代软件开发需求的健壮高效的 JavaScript 应用程序。
文章来源:https://dev.to/topefasasi/js-design-patterns-a-compressive-guide-h3m