写点东西《JavaScript 中的设计模式:综合指南》

发布时间:2024年01月11日

写点东西《JavaScript 中的设计模式:综合指南》

JavaScript 因其广泛采用和多功能性,已成为现代 Web 开发的基石。随着您深入研究 JavaScript 开发,理解和利用模式变得至关重要。在本文中,我们将踏上揭开 JavaScript 模式的神秘面纱的旅程,并探讨它们如何增强您的编码实践。

先决条件

要理解本文中讨论的概念和技术,您应该了解 JavaScript 的基础知识。熟悉变量、函数、数据类型、面向对象编程等概念至关重要。

在继续之前,让我们花点时间来了解 JavaScript 作为一种编程语言的重要性。

JavaScript 作为一种编程语言

JavaScript,通常被称为“网络语言”,是一种动态的高级编程语言。它主要用于网络浏览器中的客户端脚本,但随着 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() 语法创建 instance1 和 instance2 时,这两个变量都引用 Singleton 类的同一个实例。因此,使用严格相等运算符比较 instance1 === instance2 时,它将计算为 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");

在此示例中,使用 new CarFactory() 创建 CarFactory 实例,然后使用参数“Tope”和“Model 1”对工厂调用 createCar 方法。这会创建一个新的汽车对象,其品牌为“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。在函数内部,name 和 age 值使用 this 关键字分配给新创建对象的相应属性。

稍后,通过使用参数“Tope”和 24 调用 Person 函数,创建 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."

在此示例中,汽车实例 car1 和 car2 是使用原型对象 carPrototype 创建的。car1 的品牌为“Toyota”,型号为“Camry”,而 car2 的品牌为“Honda”,型号为“Accord”。调用 car1.startEngine() 时,它会输出“发动机已启动。”,调用 car2.stopEngine() 时,它会输出“发动机已停止。”。这演示了利用原型对象在多个实例之间共享属性和方法。

建造者模式

在建造者模式中,一个建造者类或对象负责构建最终对象。它提供了一组方法来配置和设置正在构建的对象的属性。构建过程通常涉及按特定顺序调用这些方法以逐步构建对象。

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 对象。通过调用 setMakesetModelsetEnginesetWheels 方法,可以设置 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 类的 Coffee 实例。然后,实例化 coffeeWithMilk 类的 CoffeeDecorator 实例,传递 myCoffee 作为参数。调用 coffeeWithMilk.getCost() 时,它返回添加了装饰器成本后的咖啡总成本,结果输出为 1.5。此示例说明了装饰器模式如何通过动态添加或修改对象的属性或方法来扩展对象的功能。

外观模式

外观模式为复杂子系统提供了一个简化的接口,充当一个隐藏底层实现细节的面向前端的接口。它通过提供一个高级别接口,提供了一种与复杂系统交互的便捷方式。

// 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."

在此示例中,代码由三个类组成: SubsystemASubsystemBFacadeSubsystemASubsystemB 类表示独立的子系统,并具有各自的 operationAoperationB 方法。 Facade 类用作简化接口,它聚合了子系统功能。

在使用部分,创建了 facade 类的 Facade 实例。调用 facade.operation() 会触发从 SubsystemA 执行 operationA ,从 SubsystemB 执行 operationB 。因此,输出显示“子系统 A 操作。”,后跟“子系统 B 操作。”。这演示了外观模式如何提供统一且简化的接口来与复杂子系统进行交互,抽象其复杂性并使其更易于使用。

适配器模式

适配器模式是一种结构型设计模式,它允许具有不兼容接口的对象通过充当它们之间的桥梁而协同工作。它提供了一种将一个对象接口转换为客户端期望的另一个接口的方法。

// 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!") 时,它会有效地触发旧版打印功能,输出“旧版打印:你好,世界!”。这展示了适配器模式如何通过提供标准化接口来实现不兼容组件的集成。

桥接模式

桥接模式是一种结构型设计模式,它将系统的抽象和实现分离,允许它们独立演化。它通过使用接口或抽象类在两者之间引入桥接。以下是一个代码片段示例来说明桥接模式:

// 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 类表示的抽象,它具有颜色属性和 draw 方法。具体抽象 Circle 和 Square 从 Shape 类继承并实现其特定的绘制行为。由 Color 类表示,它声明 getColor 方法。具体 ImplementorsRedColorBlueColor 从 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();

在此示例中,我们有 Component 类 Employee,它表示单个员工。Composite 类 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 方法。因此,输出“观察者已收到更改通知。”被记录了两次,表明观察者已收到有关主题更改的通知。

策略模式

策略模式允许您将可互换算法封装在单独的策略对象中。它支持在运行时动态选择算法,从而提高了灵活性和可扩展性。

// 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 方法。有两个具体的策略类, ConcreteStrategyAConcreteStrategyB ,每个类都有自己的 execute 方法,该方法输出特定消息。

在用法部分,使用 ConcreteStrategyA 作为策略创建 Context 类的 contextA 实例。调用 contextA.executeStrategy() 会调用 ConcreteStrategyAexecute 方法,从而输出“执行策略 A”。同样,使用 ConcreteStrategyB 作为策略创建 contextB 实例,调用 contextB.executeStrategy() 会触发 ConcreteStrategyBexecute 方法,从而输出“执行策略 B”。这演示了策略模式如何通过将行为封装在不同的策略对象中,允许在运行时动态选择行为。

命令模式

命令模式将请求封装成对象,允许你使用不同的请求对客户端进行参数化,对请求进行排队或记录,并支持撤销操作。它将请求的发送者和接收者解耦,从而提高松散耦合和灵活性。

// 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 类表示,该类定义了用于访问和遍历元素的接口。具体迭代器 ConcreteIterator 扩展了 Iterator 类,并提供了迭代逻辑的具体实现。在使用部分,我们创建了具体聚合 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!"

在这个例子中,我们有一个中介者类,它充当两个同事对象之间的中介。中介者持有对同事的引用,并提供在它们之间发送消息的方法。

每个 Colleague 对象都引用中介者,并可以通过通知中介者来发送消息。中介者继而将消息中继给适当的同事。在本例中,Colleague 1 向 Colleague 2 发送消息,后者接收并记录该消息。

结论

我们已经探索了 JavaScript 中的一系列基本设计模式,包括创建型、结构型和行为型模式。创建型模式允许我们以灵活高效的方式创建对象。结构型模式有助于提高组织的灵活性与可扩展性。行为型模式支持 JavaScript 对象之间的有效通信和交互。通过利用这些设计模式,JavaScript 开发人员可以提高代码的可重用性、可维护性和整体系统性能。掌握了这些知识,我们就可以构建满足现代软件开发需求的强大且高效的 JavaScript 应用程序。

文章来源:https://blog.csdn.net/l316278750/article/details/135450960
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。