JavaScript 中的 OOP 设计模式 理解 JavaScript 中的设计模式

2025-05-25

JavaScript 中的 OOP 设计模式

理解 JavaScript 中的设计模式

理解 JavaScript 中的设计模式

在编写简洁、可维护且高效的代码方面,设计模式在软件开发领域发挥着至关重要的作用。设计模式是针对开发人员在设计和构建软件系统时遇到的常见问题的可复用解决方案。它们提供了一种结构化的方法来解决特定挑战,使创建不仅健壮而且易于理解和维护的代码变得更加容易。

在面向对象编程 (OOP) 中,设计模式作为构建代码的指导原则,旨在提升灵活性、可重用性和可扩展性。它们囊括了最佳实践和设计原则,这些实践和原则随着时间的推移不断演变,最终提炼为行之有效的解决方案。

设计模式的类别

设计模式可以分为三大类:

  1. 创建型模式:这类模式专注于对象创建机制,尝试以适合具体情况的方式创建对象。它们抽象了实例化过程,使其更加灵活,并且独立于系统。

  2. 结构模式:结构模式处理对象组合,建立对象之间的关系,从而创建更大、更复杂的结构。它们有助于定义如何组合对象和类以形成新的结构并提供新的功能。

  3. 行为模式:行为模式关注对象之间的通信,定义它们如何交互以及如何分配职责。这些模式可以帮助你设计对象之间以更灵活、更高效的方式协作的系统。

常见的设计模式

以下列出了每个类别中的一些常见设计模式:

创建模式

  1. 单例模式:确保一个类只有一个实例,并提供对该实例的全局访问点。
  2. 工厂方法模式:定义用于创建对象的接口,但允许子类改变将要创建的对象的类型。
  3. 抽象工厂模式:提供一个接口,用于创建相关或依赖的对象系列,而无需指定它们的具体类。
  4. 建造者模式:将复杂对象的构造与其表示分离,允许相同的构造过程创建不同的表示。
  5. 原型模式:通过复制现有对象(称为原型)来创建新对象。
  6. 对象池模式:管理可重用对象池,以最大限度地减少对象创建和销毁的开销。

结构模式

  1. 适配器模式:允许将现有类的接口用作另一个接口。
  2. 装饰器模式:动态地为对象附加额外的职责,为子类化提供灵活的替代方案。
  3. 代理模式:为另一个对象提供代理或占位符以控制对它的访问。
  4. 复合模式:将对象组合成树结构来表示部分-整体层次结构。
  5. 桥接模式:将对象的抽象与其实现分离,允许两者独立变化。
  6. 享元模式:通过尽可能多地共享相关对象来最大限度地减少内存使用或计算费用。

行为模式

  1. 观察者模式:定义对象之间的一对多依赖关系,因此当一个对象改变状态时,其所有依赖者都会收到通知并自动更新。
  2. 策略模式:定义一组算法,封装每个算法,并使它们可以互换。
  3. 命令模式:将请求封装为对象,从而允许使用队列、请求和操作对客户端进行参数化。
  4. 状态模式:允许对象在其内部状态改变时改变其行为,将行为包装在单独的类中。
  5. 责任链模式:沿着处理程序链传递请求,允许每个处理程序决定处理请求或将其传递给链中的下一个处理程序。
  6. 访问者模式:表示对对象结构的元素执行的操作,使您能够定义新的操作而无需更改元素的类。

在此博客中,我们将深入研究每一种设计模式,提供解释、实际用例和 JavaScript 代码示例,以帮助您在项目中理解和有效地实现它们。

JavaScript 中的单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。当你想要限制应用程序中某个类的实例数量,并控制对单个共享实例的访问时,此模式尤其有用。

在 JavaScript 中,由于语言的灵活性,实现单例模式相对简单。让我们深入研究一个简单的示例,了解如何在 JavaScript 中创建单例。

示例实现

// Singleton instance
let instance = null;

class Singleton {
  constructor() {
    if (!instance) {
      instance = this;
      // Your initialization code here
    } else {
      return instance;
    }
  }

  // Your methods and properties here
}

// Usage
const singletonA = new Singleton();
const singletonB = new Singleton();


console.log(singletonA === singletonB); // Output: true (both variables reference the same instance)
Enter fullscreen mode Exit fullscreen mode

在此示例中,我们创建了一个 Singleton 类,并使用构造函数检查实例是否已存在。如果实例不存在,则创建一个并将其赋值给实例变量。后续调用构造函数时,将返回现有实例,从而确保 Singleton 类只有一个实例。

真实用例

单例模式在各种场景中都很有用,包括:

管理配置设置:您可以使用 Singleton 来管理应用程序的配置设置,确保配置值只有一个真实来源。

记录器和错误处理:可以使用单例来维护集中式日志记录或错误处理机制,从而允许您合并日志条目或错误消息。

数据库连接:处理数据库时,您可能希望使用 Singleton 来确保整个应用程序只共享一个数据库连接,以最大限度地减少资源消耗。

缓存:实现单例来缓存常用数据可以通过维护单个缓存实例来帮助优化性能。

注意事项

虽然单例模式有很多好处,但务必谨慎使用。过度使用单例模式会导致代码紧耦合和全局状态,这可能会使应用程序更难维护和测试。因此,务必权衡利弊,并在真正能为代码库增值的地方应用该模式。

JavaScript 中的工厂和抽象工厂模式

工厂模式和抽象工厂模式都是创建型设计模式,用于处理对象的创建,但它们的实现方式不同,用途也各异。让我们逐一探索这两种模式,看看如何在 JavaScript 中实现它们。

工厂模式

工厂模式是一种创建型模式,它提供了创建对象的接口,但允许子类更改要创建对象的类型。它封装了对象创建过程,使其更加灵活,并与客户端代码解耦。

示例实现

// Product class
class Product {
  constructor(name) {
    this.name = name;
  }
}

// Factory for creating products
class ProductFactory {
  createProduct(name) {
    return new Product(name);
  }
}

// Usage
const factory = new ProductFactory();
const productA = factory.createProduct('Product A');
const productB = factory.createProduct('Product B');

console.log(productA.name); // Output: 'Product A'
console.log(productB.name); // Output: 'Product B'
Enter fullscreen mode Exit fullscreen mode

在此示例中,ProductFactory 负责创建 Product 类的实例。它抽象了创建过程,允许您通过扩展工厂来创建不同类型的产品。

抽象工厂模式

抽象工厂模式是另一种创建型模式,它提供了一个接口,用于创建一系列相关或依赖的对象,而无需指定它们的具体类。它允许你创建一组能够协同工作的对象。

示例实现

// Abstract Product classes
class Button {
  render() {}
}

class Checkbox {
  render() {}
}

// Concrete Product classes
class MacButton extends Button {
  render() {
    return 'Render Mac button';
  }
}

class MacCheckbox extends Checkbox {
  render() {
    return 'Render Mac checkbox';
  }
}

class WindowsButton extends Button {
  render() {
    return 'Render Windows button';
  }
}

class WindowsCheckbox extends Checkbox {
  render() {
    return 'Render Windows checkbox';
  }
}

// Abstract Factory interface
class GUIFactory {
  createButton() {}
  createCheckbox() {}
}

// Concrete Factories
class MacFactory extends GUIFactory {
  createButton() {
    return new MacButton();
  }

  createCheckbox() {
    return new MacCheckbox();
  }
}

class WindowsFactory extends GUIFactory {
  createButton() {
    return new WindowsButton();
  }

  createCheckbox() {
    return new WindowsCheckbox();
  }
}

// Usage
function createUI(factory) {
  const button = factory.createButton();
  const checkbox = factory.createCheckbox();

  return { button, checkbox };
}

const macUI = createUI(new MacFactory());
console.log(macUI.button.render()); // Output: 'Render Mac button'
console.log(macUI.checkbox.render()); // Output: 'Render Mac checkbox'

const windowsUI = createUI(new WindowsFactory());
console.log(windowsUI.button.render()); // Output: 'Render Windows button'
console.log(windowsUI.checkbox.render()); // Output: 'Render Windows checkbox'
Enter fullscreen mode Exit fullscreen mode

在此示例中,我们有两个具体工厂:MacFactory 和 WindowsFactory,每个工厂都能够为各自的平台创建一组相关的 UI 组件(按钮和复选框)。createUI 函数允许您使用适当的工厂为特定平台创建具有凝聚力的 UI。

何时使用哪种模式

  • 当您想要封装对象创建过程并提供一个用于创建具有不同实现的对象简单的接口时,请使用工厂模式。

  • 当你需要创建一系列必须协同工作的相关或依赖对象时,可以使用抽象工厂模式。它有助于确保创建的对象兼容且具有内聚性。

JavaScript 中的建造者模式

建造者模式是一种创建型设计模式,它将复杂对象的构造与其表现形式分离,允许使用相同的构造过程创建不同的表现形式。当你的对象具有大量属性,并且希望在保持灵活性的同时简化实例的创建时,此模式尤其有用。

在 JavaScript 中,建造者模式通常使用建造者类或对象来实现,它们会指导复杂对象的逐步构建。让我们深入研究一个例子来了解它的工作原理。

示例实现

// Product class with multiple properties
class Product {
  constructor() {
    this.name = '';
    this.price = 0;
    this.color = 'white';
    // ... other properties
  }

  // Additional methods can be defined here
}

// Builder for creating Product instances
class ProductBuilder {
  constructor() {
    this.product = new Product();
  }

  setName(name) {
    this.product.name = name;
    return this; // Return the builder for method chaining
  }

  setPrice(price) {
    this.product.price = price;
    return this;
  }

  setColor(color) {
    this.product.color = color;
    return this;
  }

  // Other methods to set additional properties

  build() {
    return this.product; // Return the fully constructed product
  }
}

// Usage
const builder = new ProductBuilder();

const productA = builder
  .setName('Product A')
  .setPrice(99.99)
  .setColor('blue')
  .build();

const productB = builder
  .setName('Product B')
  .setPrice(49.99)
  .build();

console.log(productA);
console.log(productB);
Enter fullscreen mode Exit fullscreen mode

在此示例中,我们有一个包含多个属性的 Product 类。ProductBuilder 类通过提供逐步设置每个属性的方法来帮助创建 Product 实例。方法链允许您以流畅且可读的方式设置多个属性。最后,build 方法返回完整构造的 Product 实例。

真实用例

建造者模式在各种场景中都有用,包括:

复杂的对象创建:当您需要创建具有许多可选或可配置属性的对象时,建造者模式可以简化构造过程。

不可变对象:构建器可用于创建不可变对象,因为您可以在构造期间设置属性,但防止之后进行修改。

参数化构造函数:建造者模式不再使用构造函数中的长参数列表,而是提供了一种更清晰、更有条理的方法来构造对象。

配置对象:在配置库或组件时,构建器可以帮助创建和定制配置对象。

注意事项

虽然建造者模式有很多优点,但需要注意的是,它会增加代码库的复杂性,尤其是在构建的对象相对简单的情况下。因此,务必评估建造者模式引入的复杂性是否适合你的具体用例。

JavaScript 中的原型模式

原型模式是一种创建型设计模式,它允许你通过复制现有对象(称为原型)来创建新对象。它简化了对象的创建过程,而无需指定要创建对象的确切类。当你想高效地创建复杂对象的实例时,此模式尤为有用。

在 JavaScript 中,原型模式与内置prototype属性和Object.create()方法紧密相关。让我们来探索如何在 JavaScript 中实现和使用原型模式。

示例实现

// Prototype object
const vehiclePrototype = {
  init(make, model) {
    this.make = make;
    this.model = model;
  },
  getDetails() {
    return `${this.make} ${this.model}`;
  },
};

// Create new instances using the prototype
const car1 = Object.create(vehiclePrototype);
car1.init('Toyota', 'Camry');

const car2 = Object.create(vehiclePrototype);
car2.init('Honda', 'Civic');

console.log(car1.getDetails()); // Output: 'Toyota Camry'
console.log(car2.getDetails()); // Output: 'Honda Civic'
Enter fullscreen mode Exit fullscreen mode

在此示例中,我们定义了一个 VehiclePrototype 对象,其中包含所有车辆共有的方法和属性。我们使用 Object.create() 基于此原型创建新的实例(car1 和 car2)。这些实例继承了原型的属性和方法,从而让您能够高效地创建具有共享行为的新对象。

真实用例

原型模式在各种场景中都很有价值,包括:

减少对象初始化开销:当需要创建具有相似结构的对象多个实例时,原型模式可以减少重复设置对象属性和方法的开销。

克隆复杂对象:如果您有具有嵌套结构的复杂对象,原型模式可以通过复制原型来简化类似对象的创建。

可配置对象创建:当您想要创建具有不同配置的对象时,您可以使用原型通过各种设置来初始化它们。

注意事项

虽然原型模式很有用,但也有一些注意事项:

浅拷贝:默认情况下,JavaScript 的 Object.create() 方法对属性执行浅拷贝。如果原型包含嵌套对象或函数,它们将在实例之间共享。如有必要,您可能需要实现深拷贝。

原型修改:修改原型上的属性或方法时要小心,因为它会影响从其创建的所有实例。

初始化:原型模式通常需要单独的初始化步骤来设置特定于实例的属性,这可能会增加复杂性。

JavaScript 中的对象池模式

对象池模式是一种创建型设计模式,它管理一个可复用对象池,以最大限度地减少对象创建和销毁的开销。当创建和销毁对象成本高昂或占用大量资源时,它尤其有用。对象池模式通过回收和复用对象(而非从头​​创建新对象)来帮助提高性能和资源利用率。

在 JavaScript 中,你可以使用数组或自定义池管理类来实现对象池模式。让我们通过一个简单的示例来探索此模式的工作原理。

示例实现

class ObjectPool {
  constructor(maxSize) {
    this.maxSize = maxSize;
    this.pool = [];
  }

  create() {
    if (this.pool.length < this.maxSize) {
      // Create a new object and add it to the pool
      const obj = { /* Your object initialization code here */ };
      this.pool.push(obj);
      return obj;
    } else {
      // Pool is full, cannot create more objects
      console.log('Pool is full. Cannot create more objects.');
      return null;
    }
  }

  reuse() {
    if (this.pool.length > 0) {
      // Reuse an object from the pool
      return this.pool.pop();
    } else {
      // Pool is empty, no objects available for reuse
      console.log('Pool is empty. No objects available for reuse.');
      return null;
    }
  }

  release(obj) {
    // Release an object back to the pool for reuse
    this.pool.push(obj);
  }
}

// Usage
const pool = new ObjectPool(5); // Create a pool with a maximum size of 5 objects

const obj1 = pool.create();
const obj2 = pool.create();
const obj3 = pool.create();

pool.release(obj2); // Release obj2 back to the pool for reuse

const obj4 = pool.reuse(); // Reuse an object from the pool (obj2)
Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们创建了一个 ObjectPool 类来管理一个对象池。create 方法用于在对象池未满时创建新的对象;reuse 方法用于从对象池中检索一个对象以供重用;release 方法用于将一个对象返回到对象池中以供将来使用。

真实用例

对象池模式在各种场景中都很有用,包括:

数据库连接:管理数据库连接可能会耗费大量资源。使用对象池可以帮助重用连接,从而提高性能并减少开销。

线程管理:在多线程环境中,可以使用对象池来管理线程,尤其是在创建线程成本较高的情况下。

资源密集型对象:对于消耗大量内存或需要时间初始化的对象,对象池模式可以减少创建和销毁实例的开销。

注意事项

虽然对象池模式提供了性能优势,但需要考虑以下几点:

资源管理:必须仔细管理资源,以确保对象在使用后正确地返回到池中。

池大小:选择合适的池大小对于平衡资源利用率和内存消耗至关重要。

线程安全:在多线程环境中,需要实现适当的同步机制来确保线程安全。

JavaScript 中的适配器模式

适配器模式是一种结构化设计模式,允许接口不兼容的对象协同工作。它充当两个不兼容接口之间的桥梁,使它们在不更改源代码的情况下兼容。当您需要集成或使用与应用程序需求不太匹配的现有代码时,此模式尤其有用。

在 JavaScript 中,适配器模式可以通过使用包装或适配不兼容接口的类或函数来实现。让我们通过一个实际的例子来探索如何在 JavaScript 中实现和使用适配器模式。

示例实现

假设您有一个现有的类,OldSystem其方法如下legacyRequest

class OldSystem {
  legacyRequest() {
    return 'Data from the legacy system';
  }
}
Enter fullscreen mode Exit fullscreen mode

现在,你想在需要不同接口的现代应用程序中使用这个遗留系统。你可以创建一个适配器类或函数,如下所示:

class Adapter {
  constructor(oldSystem) {
    this.oldSystem = oldSystem;
  }

  newRequest() {
    const legacyData = this.oldSystem.legacyRequest();
    // Adapt the data or perform any necessary transformations
    return `Adapted: ${legacyData}`;
  }
}
Enter fullscreen mode Exit fullscreen mode

现在,您可以使用 Adapter 类使遗留系统与现代应用程序兼容:

const oldSystem = new OldSystem();
const adapter = new Adapter(oldSystem);

const result = adapter.newRequest();
console.log(result); // Output: 'Adapted: Data from the legacy system'
Enter fullscreen mode Exit fullscreen mode

在这个例子中,Adapter 类包装了 OldSystem 并提供了一个新的接口 newRequest,它与您的现代应用程序兼容。

真实用例

适配器模式在各种场景中都很有价值,包括:

集成遗留代码:当您需要将遗留系统或库与现代代码库集成时,适配器可以帮助弥合两者之间的差距。

第三方库:当使用具有不兼容接口的第三方库或 API 时,适配器可以使它们符合应用程序的要求。

测试:适配器可用于在测试期间创建模拟对象或模拟接口以隔离依赖关系。

版本兼容性:可以使用适配器来保持与不同版本的 API 或库的兼容性。

注意事项

虽然适配器模式提供了灵活性和兼容性,但必须考虑以下几点:

性能:适配器可能会因额外的方法调用和数据转换而带来一些开销。请根据需要进行测量和优化。

可维护性:保持适配器代码清洁且有据可查,以确保未来的开发人员了解适配器的用途和用法。

接口复杂度:注意不要创建过于复杂、试图实现过多功能的适配器。应使其专注于特定的适配任务。

JavaScript 中的装饰器模式

装饰器模式是一种结构化设计模式,它允许你动态地为对象添加新的行为或职责,而无需修改现有代码。这是一种强大的扩展对象功能的方法,即用装饰器对象包装对象。该模式遵循“对扩展开放,对修改关闭”的原则,让你可以轻松地在不更改对象核心实现的情况下为其添加新功能。

在 JavaScript 中,装饰器模式可以通过类和对象组合来实现。让我们通过一个实际的例子来探索如何在 JavaScript 中实现和使用装饰器模式。

示例实现

假设你有一个基类Coffee

class Coffee {
  cost() {
    return 5; // Base cost of a regular coffee
  }
}
Now, you want to add decorators to your coffee to customize it with additional options, such as milk and sugar:

javascript
Copy code
class MilkDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost() + 2; // Adding the cost of milk
  }
}

class SugarDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost() + 1; // Adding the cost of sugar
  }
}
Enter fullscreen mode Exit fullscreen mode

然后,您可以像这样创建装饰咖啡实例:

const regularCoffee = new Coffee();
const coffeeWithMilk = new MilkDecorator(regularCoffee);
const coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);

console.log(regularCoffee.cost()); // Output: 5
console.log(coffeeWithMilk.cost()); // Output: 7
console.log(coffeeWithMilkAndSugar.cost()); // Output: 8
Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们用 Coffee 类来表示基础咖啡。MilkDecorator 和 SugarDecorator 类是装饰器,它们包装咖啡对象,并分别将牛奶和糖的成本添加到基础咖啡成本中。

真实用例

装饰器模式在各种场景中都很有价值,包括:

扩展类:您可以使用装饰器向类添加功能,而无需修改其源代码,从而轻松引入新功能。

动态组合:装饰器允许在运行时动态组合对象,使您能够从简单的组件构建复杂的对象。

定制:您可以通过应用不同的装饰器组合来定制对象,从而提供灵活性和可配置性。

日志记录和分析:装饰器可用于记录或分析方法调用,而无需修改原始类。

注意事项

虽然装饰器模式用途广泛,但务必牢记以下几点注意事项:

装饰顺序:应用装饰器的顺序会影响最终行为,因此请注意包装对象的顺序。

复杂性:过度使用装饰器可能会导致代码复杂而难懂,因此请仔细考虑装饰器是否是适合您用例的最佳解决方案。

接口兼容性:确保装饰器遵守通用接口或契约,以保持与其装饰的对象的兼容性。

JavaScript 中的代理模式

代理模式是一种结构化设计模式,它为另一个对象提供代理或占位符,以控制对其的访问。它充当目标对象的中介或包装器,允许您添加其他行为、控制访问或延迟对象创建。代理模式在各种场景中都很有用,例如实现延迟加载、访问控制和日志记录。

在 JavaScript 中,可以使用内置Proxy对象创建代理。让我们通过实际示例来探索如何在 JavaScript 中实现和使用代理模式。

示例实现

使用代理进行延迟加载

假设你有一个资源密集型对象,你希望只在需要时才延迟加载。你可以使用代理来实现延迟加载:

class ExpensiveResource {
  constructor() {
    console.log('Creating an expensive resource...');
  }

  fetchData() {
    console.log('Fetching data...');
  }
}

class LazyResourceProxy {
  constructor() {
    this.resource = null;
  }

  fetchData() {
    if (!this.resource) {
      this.resource = new ExpensiveResource();
    }
    this.resource.fetchData();
  }
}

// Usage
const lazyResource = new LazyResourceProxy();
// The actual resource is created and data is fetched only when needed
lazyResource.fetchData();
Enter fullscreen mode Exit fullscreen mode

在这个例子中,LazyResourceProxy 充当 ExpensiveResource 的代理,仅在第一次调用 fetchData 方法时创建实际资源。

使用代理进行访问控制

您还可以使用代理来控制对对象及其属性的访问:

const user = {
  username: 'john_doe',
  password: 'secret123',
};

const userProxy = new Proxy(user, {
  get(target, property) {
    if (property === 'password') {
      throw new Error('Access denied to password.');
    }
    return target[property];
  },
});

console.log(userProxy.username); // Output: 'john_doe'
console.log(userProxy.password); // Throws an error: 'Access denied to password.'
Enter fullscreen mode Exit fullscreen mode

在这个例子中,代理拦截了 get 操作并限制了对密码属性的访问。

真实用例

代理模式在各种场景中都很有价值,包括:

延迟加载:您可以使用代理来推迟资源密集型对象的创建和初始化,直到真正需要它们为止。

访问控制:代理可以强制执行访问控制策略,允许您限制或授予对某些属性或方法的访问权限。

缓存:代理可以实现缓存机制,通过存储和返回缓存数据而不是进行昂贵的操作来提高性能。

日志记录和分析:代理可以记录或分析方法调用,帮助您深入了解对象的行为。

注意事项

使用代理模式时,请记住以下注意事项:

开销:代理可能会因拦截操作而产生一些开销。请注意性能影响,尤其是在性能至关重要的代码中。

兼容性:确保代理遵循与目标对象相同的接口,以保持与现有代码的兼容性。

安全性:虽然代理可以帮助控制访问,但不应将其作为唯一的安全措施。可能需要采取额外的安全措施,尤其是在服务器端。

JavaScript 中的复合模式

组合模式是一种结构化设计模式,它允许你将对象组合成树状结构,以表示“部分-整体”的层次结构。它允许客户端统一处理单个对象及其组合。当你需要处理由较小且相关的对象组成的复杂结构,同时又要保持一致的接口时,组合模式尤其有用。

在 JavaScript 中,你可以使用共享通用接口的类或对象来实现复合模式,从而构建层次结构。让我们通过实际示例来探索如何在 JavaScript 中实现和使用复合模式。

示例实现

假设你正在构建一个图形设计应用程序,它既需要处理简单形状,也需要处理复杂的形状组合(例如,组)。你可以使用组合模式来表示这种层次结构:

// Component interface
class Graphic {
  draw() {}
}

// Leaf class (represents simple shapes)
class Circle extends Graphic {
  constructor() {
    super();
    // Circle-specific properties and methods
  }

  draw() {
    // Draw a circle
  }
}

// Composite class (represents groups of shapes)
class Group extends Graphic {
  constructor() {
    super();
    this.graphics = [];
  }

  add(graphic) {
    this.graphics.push(graphic);
  }

  draw() {
    // Draw each graphic in the group
    this.graphics.forEach((graphic) => graphic.draw());
  }
}

// Usage
const circle1 = new Circle();
const circle2 = new Circle();
const group = new Group();

group.add(circle1);
group.add(circle2);

group.draw(); // Draws both circles in the group
Enter fullscreen mode Exit fullscreen mode

在此示例中,Graphic 类用作组件接口。Circle 类表示简单形状,而 Group 类表示形状的组合。Circle 和 Group 类都实现了 draw 方法,因此您可以在渲染时统一处理它们。

真实用例

组合模式在各种场景中都很有价值,包括:

图形和 UI 框架:用于表示复杂的用户界面或图形场景,您需要一致地处理单个元素和元素组。

文件系统:复合模式可以表示分层文件系统,其中文件和目录共享一个公共接口。

组织结构:它可用于模拟组织结构,例如公司内的部门或大学内的分部。

嵌套组件:当您拥有可以包含其他组件的组件(例如,包含输入字段的表单)时,复合模式有助于管理结构。

注意事项

使用复合模式时,请考虑以下事项:

复杂性:虽然该模式简化了复杂结构的工作,但它也可能带来复杂性,尤其是在处理深层层次结构时。

统一接口:确保所有组件(叶子和复合材料)共享一个公共接口以保持一致性。

性能:根据实现情况,遍历复合结构可能会对性能产生影响,因此请根据需要进行优化。

JavaScript 中的桥接模式

桥接模式是一种结构化设计模式,它将对象的抽象与实现分离。它允许你在两者之间搭建桥梁,使它们能够独立变化。当你想避免抽象与其实现之间的永久绑定时,此模式尤其有用,它能使你的代码更加灵活且易于维护。

在 JavaScript 中,桥接模式可以通过类和对象来实现,这些类和对象为抽象层提供抽象接口,并为不同的平台或功能提供不同的具体实现。让我们通过实际示例来探索如何在 JavaScript 中实现和使用桥接模式。

示例实现

假设你正在构建一个可以在不同平台(例如 Web 浏览器和移动设备)上渲染形状的绘图应用程序。你可以使用桥接模式将绘制形状(抽象)与渲染逻辑(实现)分离:

// Abstraction
class Shape {
  constructor(renderer) {
    this.renderer = renderer;
  }

  draw() {
    // Delegating the drawing to the specific renderer
    this.renderer.renderShape(this);
  }
}

// Implementor interface
class Renderer {
  renderShape(shape) {}
}

// Concrete Implementors
class WebRenderer extends Renderer {
  renderShape(shape) {
    console.log(`Drawing on the web: ${shape.constructor.name}`);
  }
}

class MobileRenderer extends Renderer {
  renderShape(shape) {
    console.log(`Drawing on mobile: ${shape.constructor.name}`);
  }
}

// Concrete Abstractions (Shapes)
class Circle extends Shape {
  constructor(renderer) {
    super(renderer);
  }
}

class Square extends Shape {
  constructor(renderer) {
    super(renderer);
  }
}

// Usage
const webRenderer = new WebRenderer();
const mobileRenderer = new MobileRenderer();

const circle = new Circle(webRenderer);
const square = new Square(mobileRenderer);

circle.draw(); // Output: Drawing on the web: Circle
square.draw(); // Output: Drawing on mobile: Square
Enter fullscreen mode Exit fullscreen mode

在此示例中,Shape 类表示抽象(要绘制的形状),Renderer 类表示实现接口(特定于平台的渲染逻辑)。不同的具体实现器(WebRenderer 和 MobileRenderer)分别为 Web 和移动平台提供渲染逻辑。Circle 和 Square 类是表示形状的具体抽象。

真实用例

桥接模式在各种场景中都很有价值,包括:

平台独立性:当您想要确保抽象和实现可以独立变化时,可以更容易地支持多个平台。

数据库驱动程序:它可用于数据库驱动程序,将数据库特定的代码(实现)与数据库操作(抽象)分离。

GUI 框架:在图形用户界面 (GUI) 框架中,桥接模式可以帮助将用户界面元素与底层窗口系统分离。

设备驱动程序:处理硬件或设备驱动程序时,此模式允许您将设备特定的代码与更高级别的应用程序代码分开。

注意事项

使用桥接模式时,请考虑以下事项:

复杂性:虽然该模式提供了灵活性,但它会增加代码库的复杂性,尤其是在处理许多抽象和实现时。

维护:确保抽象和实现在发生变化时保持同步,因为它们可以独立发展。

接口设计:仔细设计抽象和实现接口,以确保它们满足应用程序的要求。

JavaScript 中的享元模式

享元模式是一种结构化设计模式,旨在通过共享对象的公共部分来减少内存消耗并提升性能。它通过将对象的内在状态(共享且不可变)与外在状态(唯一且依赖于上下文)分离来实现这一点。当您拥有大量相似的对象并希望最大限度地减少内存占用时,此模式尤其有用。

在 JavaScript 中,你可以使用类或对象来实现享元模式,以表示共享的内部状态和独立的外部状态。让我们通过实际示例来探索如何在 JavaScript 中实现和使用享元模式。

示例实现

假设你正在开发一个需要显示大量文本的文本编辑器。你不需要为每个字符创建一个单独的对象,而是可以使用享元模式,当字符对象具有相同的固有属性(例如字体和大小)时,共享它们:

class Character {
  constructor(char, font, size) {
    this.char = char;
    this.font = font;
    this.size = size;
  }

  render() {
    console.log(`Rendering character "${this.char}" in ${this.font}, size ${this.size}`);
  }
}

class CharacterFactory {
  constructor() {
    this.characters = {};
  }

  getCharacter(char, font, size) {
    const key = `${char}-${font}-${size}`;
    if (!this.characters[key]) {
      this.characters[key] = new Character(char, font, size);
    }
    return this.characters[key];
  }
}

// Usage
const factory = new CharacterFactory();

const charA1 = factory.getCharacter('A', 'Arial', 12);
const charA2 = factory.getCharacter('A', 'Arial', 12);
const charB = factory.getCharacter('B', 'Times New Roman', 14);

charA1.render(); // Output: Rendering character "A" in Arial, size 12
charA2.render(); // Output: Rendering character "A" in Arial, size 12 (shared instance)
charB.render();  // Output: Rendering character "B" in Times New Roman, size 14
Enter fullscreen mode Exit fullscreen mode

在此示例中,Character 类表示具有固有属性(例如字符本身、字体和大小)的单个字符。CharacterFactory 类确保具有相同固有属性的字符可以共享,而不会重复。

真实用例

享元模式在很多场景下都很有价值,包括:

文本处理:处理大量文本时,通过共享通用字符、字体或其他与文本相关的属性,可以显著减少内存消耗。

游戏开发:在游戏开发中,它用于优化具有某些特征(例如纹理或材质)的对象的渲染。

用户界面(UI):可应用于具有共享样式、字体或图标的 UI 组件,以最大限度地减少资源使用。

缓存:该模式可用于缓存经常使用的对象或数据以提高性能。

注意事项

使用享元模式时,请考虑以下几点:

识别内在状态:仔细识别并区分对象的内在状态和外在状态。内在状态应该共享,而外在状态可以变化。

线程安全:如果您的应用程序是多线程的,请确保 Flyweight 对象是线程安全的。

内存与性能:虽然享元模式减少了内存使用量,但由于需要查找和共享实例,它可能会带来轻微的性能开销。

JavaScript 中的观察者模式

观察者模式是一种行为设计模式,它在对象之间建立一对多的依赖关系。它允许一个对象(主体或可观察对象)将其状态或数据的变化通知给多个观察者(监听者)。此模式通常用于实现分布式事件处理系统,其中一个对象的状态变化会触发其他依赖对象的操作。

在 JavaScript 中,你可以使用自定义类或内置功能(例如事件监听器和addEventListener方法)来实现观察者模式。让我们通过实际示例来探索如何在 JavaScript 中实现和使用观察者模式。

示例实现

自定义观察者模式

假设你正在开发一个天气应用,并且希望 UI 的不同部分随着天气状况的变化而更新。你可以使用观察者模式的自定义实现:

class WeatherStation {
  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(this);
    });
  }

  setWeatherData(weatherData) {
    this.weatherData = weatherData;
    this.notifyObservers();
  }
}

class WeatherDisplay {
  update(weatherStation) {
    console.log(`Current weather: ${weatherStation.weatherData}`);
  }
}

// Usage
const weatherStation = new WeatherStation();
const display1 = new WeatherDisplay();
const display2 = new WeatherDisplay();

weatherStation.addObserver(display1);
weatherStation.addObserver(display2);

weatherStation.setWeatherData('Sunny'); // Both displays update with the new weather data
Enter fullscreen mode Exit fullscreen mode

在此示例中,WeatherStation 充当主体,在天气数据发生变化时通知观察者(显示对象)。观察者使用 addObserver 方法订阅主体,并实现 update 方法以对变化做出反应。

使用事件监听器

JavaScript 还提供了一种内置方法,使用事件监听器来实现观察者模式:

class NewsPublisher {
  constructor() {
    this.subscribers = [];
  }

  subscribe(subscriber) {
    this.subscribers.push(subscriber);
  }

  unsubscribe(subscriber) {
    const index = this.subscribers.indexOf(subscriber);
    if (index !== -1) {
      this.subscribers.splice(index, 1);
    }
  }

  publishNews(news) {
    this.subscribers.forEach((subscriber) => {
      subscriber(news);
    });
  }
}

// Usage
const publisher = new NewsPublisher();

const subscriber1 = (news) => {
  console.log(`Subscriber 1 received news: ${news}`);
};

const subscriber2 = (news) => {
  console.log(`Subscriber 2 received news: ${news}`);
};

publisher.subscribe(subscriber1);
publisher.subscribe(subscriber2);

publisher.publishNews('Breaking News: Important Announcement');
Enter fullscreen mode Exit fullscreen mode

在此示例中,NewsPublisher 充当主题,并使用 subscribe 方法添加订阅者(函数)。publishNews 方法通过使用新闻调用订阅者的函数来通知订阅者。

真实用例

观察者模式在各种场景中都很有价值,包括:

用户界面:在图形用户界面 (GUI) 中实现事件处理,其中 UI 组件对用户操作做出反应。

发布-订阅系统:构建发布-订阅系统,用于将消息或事件分发给多个订阅者。

模型-视图-控制器 (MVC):将模型(数据)与视图(UI)分离,并将模型的变化通知视图。

自定义事件处理:创建自定义事件驱动系统来管理状态变化和交互。

注意事项

使用观察者模式时,请考虑以下几点:

内存管理:当观察者持有目标对象的引用时,请注意内存泄漏。确保在不再需要观察者时,正确移除它们。

通知顺序:在某些情况下,通知观察者的顺序可能很重要。请确保该顺序符合应用程序的要求。

事件处理:使用内置事件处理机制时,请注意 DOM 中的事件传播和冒泡(如果适用)。

JavaScript 中的策略模式

策略模式是一种行为设计模式,它允许你定义一系列可互换的算法,封装每个算法,并使它们可互换。它使客户端能够在运行时动态选择合适的算法。该模式通过将算法的行为与使用它的上下文分离,提高了灵活性和可重用性。

在 JavaScript 中,你可以使用对象或函数来实现策略模式,这些对象代表不同的策略,并使用上下文对象来在这些策略之间切换。让我们通过实际示例来探索如何在 JavaScript 中实现和使用策略模式。

示例实现

假设您正在开发一个电子商务应用程序,并且想要计算不同类型的客户的折扣。您可以使用策略模式来封装折扣策略:

// Discount Strategies
const regularCustomerDiscount = (amount) => amount * 0.1; // 10% discount
const premiumCustomerDiscount = (amount) => amount * 0.2; // 20% discount

// Context
class ShoppingCart {
  constructor(discountStrategy) {
    this.items = [];
    this.discountStrategy = discountStrategy;
  }

  addItem(item) {
    this.items.push(item);
  }

  calculateTotal() {
    const subtotal = this.items.reduce((total, item) => total + item.price, 0);
    return subtotal - this.discountStrategy(subtotal);
  }
}

// Usage
const regularCustomerCart = new ShoppingCart(regularCustomerDiscount);
const premiumCustomerCart = new ShoppingCart(premiumCustomerDiscount);

regularCustomerCart.addItem({ name: 'Item 1', price: 50 });
premiumCustomerCart.addItem({ name: 'Item 2', price: 100 });

console.log(`Regular Customer Total: $${regularCustomerCart.calculateTotal()}`); // Output: $45 (after 10% discount)
console.log(`Premium Customer Total: $${premiumCustomerCart.calculateTotal()}`); // Output: $80 (after 20% discount)
Enter fullscreen mode Exit fullscreen mode

在此示例中,我们将两个折扣策略定义为函数(regularCustomerDiscount 和 premiumCustomerDiscount)。ShoppingCart 类将折扣策略作为参数,并根据所选策略计算总价。

真实用例

策略模式在各种场景中都很有价值,包括:

算法选择:当您需要从算法系列中动态地选择一种算法时。

配置和设置:使用不同的行为选项配置应用程序,例如排序算法或数据存储策略。

可定制的行为:通过提供不同的策略允许用户定制和扩展应用程序的行为。

测试和模拟:在单元测试中,您可以使用策略模式提供组件的模拟实现以供测试。

注意事项

使用策略模式时,请考虑以下事项:

清晰分离:确保上下文和策略之间清晰分离,以维护干净且可维护的代码库。

动态切换:动态切换策略的能力是此模式的关键特性。请确保您的设计支持这种灵活性。

策略初始化:关注策略如何初始化并传递给上下文,以确保使用正确的策略。

JavaScript 中的命令模式

命令模式是一种行为设计模式,它将请求或简单操作转换为独立的对象。它允许您使用不同的请求参数化对象,延迟或排队请求的执行,并支持可撤消的操作。此模式将请求的发送者与接收者解耦,使代码易于扩展和维护。

在 JavaScript 中,您可以使用对象或类来实现命令模式,这些对象或类表示命令以及执行这些命令的调用程序。让我们通过实际示例来探索如何在 JavaScript 中实现和使用命令模式。

示例实现

假设你正在开发一个智能家居的远程控制应用,并且想要创建一种灵活的方式来控制各种设备。你可以使用命令模式:

// Command interface
class Command {
  execute() {}
}

// Concrete Commands
class LightOnCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

  execute() {
    this.light.turnOn();
  }
}

class LightOffCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }

  execute() {
    this.light.turnOff();
  }
}

// Receiver (Device)
class Light {
  turnOn() {
    console.log('Light is on.');
  }

  turnOff() {
    console.log('Light is off.');
  }
}

// Invoker (Remote Control)
class RemoteControl {
  constructor() {
    this.commands = [];
  }

  addCommand(command) {
    this.commands.push(command);
  }

  executeCommands() {
    this.commands.forEach((command) => {
      command.execute();
    });
  }
}

// Usage
const livingRoomLight = new Light();
const kitchenLight = new Light();

const livingRoomLightOn = new LightOnCommand(livingRoomLight);
const livingRoomLightOff = new LightOffCommand(livingRoomLight);
const kitchenLightOn = new LightOnCommand(kitchenLight);
const kitchenLightOff = new LightOffCommand(kitchenLight);

const remoteControl = new RemoteControl();

remoteControl.addCommand(livingRoomLightOn);
remoteControl.addCommand(kitchenLightOff);

remoteControl.executeCommands();
// Output: "Light is on." (for living room)
// Output: "Light is off." (for kitchen)
Enter fullscreen mode Exit fullscreen mode

本例中,使用命令模式封装了开灯和关灯的动作,RemoteControl 作为调用者,具体命令(例如 LightOnCommand 和 LightOffCommand)封装了需要执行的动作。

真实用例

命令模式在各种场景中都很有价值,包括:

GUI 应用程序:它通常用于图形用户界面 (GUI) 中以实现撤消和重做功能,其中每个用户操作都封装为一个命令。

远程控制系统:在智能设备的远程控制应用中,它提供了一种使用不同命令控制各种设备的灵活方法。

批处理:当您需要排队并执行一系列具有不同参数或设置的请求或任务时。

事务管理:在数据库系统中,可以将数据库操作封装为命令,支持事务行为。

注意事项

使用命令模式时,请考虑以下几点:

命令抽象:确保命令被适当抽象,封装单个动作或操作。

撤消和重做:如果您需要撤消和重做功能,请实现必要的机制来支持撤销命令。

复杂性:注意创建多个命令类所带来的复杂性,特别是在存在大量可能命令的场景中。

JavaScript 中的状态模式

状态模式是一种行为设计模式,允许对象在其内部状态发生变化时改变其行为。它将状态封装为单独的类,并将行为委托给当前状态对象。此模式有助于管理复杂的状态转换,并遵循“开放-封闭”原则,使得无需修改现有代码即可轻松添加新状态。

在 JavaScript 中,你可以使用类来表示状态,并使用上下文对象将其行为委托给当前状态来实现状态模式。让我们通过实际示例来探索如何在 JavaScript 中实现和使用状态模式。

示例实现

假设你正在开发一台自动售货机,它可以售出不同的商品。自动售货机的行为取决于其当前状态,例如“就绪”、“售出”或“售罄”。你可以使用状态模式来建模此行为:

// State interface
class VendingMachineState {
  insertMoney() {}
  ejectMoney() {}
  selectProduct() {}
  dispenseProduct() {}
}

// Concrete States
class ReadyState extends VendingMachineState {
  constructor(machine) {
    super();
    this.machine = machine;
  }

  insertMoney() {
    console.log('Money inserted.');
    this.machine.setState(this.machine.getDispensingState());
  }

  selectProduct() {
    console.log('Please insert money first.');
  }
}

class DispensingState extends VendingMachineState {
  constructor(machine) {
    super();
    this.machine = machine;
  }

  dispenseProduct() {
    console.log('Product dispensed.');
    this.machine.setState(this.machine.getReadyState());
  }
}

class VendingMachine {
  constructor() {
    this.readyState = new ReadyState(this);
    this.dispensingState = new DispensingState(this);
    this.currentState = this.readyState;
  }

  setState(state) {
    this.currentState = state;
  }

  getReadyState() {
    return this.readyState;
  }

  getDispensingState() {
    return this.dispensingState;
  }

  insertMoney() {
    this.currentState.insertMoney();
  }

  selectProduct() {
    this.currentState.selectProduct();
  }

  dispenseProduct() {
    this.currentState.dispenseProduct();
  }
}

// Usage
const vendingMachine = new VendingMachine();

vendingMachine.selectProduct(); // Output: "Please insert money first."
vendingMachine.insertMoney();   // Output: "Money inserted."
vendingMachine.dispenseProduct(); // Output: "Product dispensed."
Enter fullscreen mode Exit fullscreen mode

在这个例子中,状态模式用于管理自动售货机的行为。“就绪”和“分配”等状态被表示为单独的类,上下文(自动售货机)将其行为委托给当前状态。

真实用例

状态模式在各种场景中都很有价值,包括:

工作流管理:管理具有不同状态和转换的应用程序的工作流,例如订单处理或批准工作流。

游戏开发:实现根据游戏状态而变化的游戏角色行为,例如“空闲”、“攻击”或“防御”。

用户界面 (UI):根据不同的用户交互或应用程序状态处理 UI 组件的行为。

有限状态机:实现用于解析、验证或网络通信的有限状态机。

注意事项

使用状态模式时,请考虑以下几点:

状态转换:确保状态转换定义明确,并且状态有效地封装其行为。

上下文管理:管理上下文的状态转换并确保它将行为正确地委托给当前状态。

复杂性:处理复杂应用程序中的许多状态和转换时,请注意可能出现的复杂性。

JavaScript 中的责任链模式

责任链模式是一种行为设计模式,它可以帮助您构建一个对象链来处理请求。链中的每个对象都有机会处理请求或将其传递给链中的下一个对象。它将请求的发送者与接收者解耦,并允许链中存在多个处理程序。此模式允许您在不影响客户端代码的情况下添加或修改处理程序,从而提高了灵活性和可扩展性。

在 JavaScript 中,您可以使用代表处理程序和发起请求的客户端的对象或类来实现责任链模式。每个处理程序都引用链中的下一个处理程序。让我们通过实际示例来探索如何在 JavaScript 中实现和使用责任链模式。

示例实现

假设您正在开发一个订单处理系统,并且希望根据订单总金额来处理订单。您可以使用责任链模式创建一个处理程序链,每个处理程序负责处理特定价格范围内的订单:

// Handler interface
class OrderHandler {
  constructor() {
    this.nextHandler = null;
  }

  setNextHandler(handler) {
    this.nextHandler = handler;
  }

  handleOrder(order) {
    if (this.canHandleOrder(order)) {
      this.processOrder(order);
    } else if (this.nextHandler) {
      this.nextHandler.handleOrder(order);
    } else {
      console.log('No handler can process this order.');
    }
  }

  canHandleOrder(order) {}
  processOrder(order) {}
}

// Concrete Handlers
class SmallOrderHandler extends OrderHandler {
  canHandleOrder(order) {
    return order.amount <= 100;
  }

  processOrder(order) {
    console.log(`Processing small order for ${order.amount}`);
  }
}

class MediumOrderHandler extends OrderHandler {
  canHandleOrder(order) {
    return order.amount <= 500;
  }

  processOrder(order) {
    console.log(`Processing medium order for ${order.amount}`);
  }
}

class LargeOrderHandler extends OrderHandler {
  canHandleOrder(order) {
    return order.amount > 500;
  }

  processOrder(order) {
    console.log(`Processing large order for ${order.amount}`);
  }
}

// Client
class Order {
  constructor(amount) {
    this.amount = amount;
  }
}

// Usage
const smallOrderHandler = new SmallOrderHandler();
const mediumOrderHandler = new MediumOrderHandler();
const largeOrderHandler = new LargeOrderHandler();

smallOrderHandler.setNextHandler(mediumOrderHandler);
mediumOrderHandler.setNextHandler(largeOrderHandler);

const order1 = new Order(80);
const order2 = new Order(250);
const order3 = new Order(600);

smallOrderHandler.handleOrder(order1); // Output: "Processing small order for 80"
smallOrderHandler.handleOrder(order2); // Output: "Processing medium order for 250"
smallOrderHandler.handleOrder(order3); // Output: "Processing large order for 600"
Enter fullscreen mode Exit fullscreen mode

在此示例中,责任链模式用于处理不同金额的订单。SmallOrderHandler、MediumOrderHandler 和 LargeOrderHandler 等处理程序会根据订单金额判断是否能够处理该订单。如果可以,则处理该订单;否则,将订单传递给链中的下一个处理程序。

真实用例

责任链模式在各种场景中都很有价值,包括:

请求处理:管理 HTTP 请求处理管道,其中每个中间件或处理程序可以处理或转发请求。

日志记录和错误处理:以结构化的方式处理日志消息或错误,每个处理程序负责特定类型的日志消息或错误情况。

事件处理:在事件驱动系统中,您可以使用此模式来处理具有多个订阅者的事件,其中每个订阅者都可以处理或过滤事件。

授权和身份验证:按顺序实施身份验证和授权检查,每个处理程序验证请求的特定方面。

注意事项

使用责任链模式时,请考虑以下几点:

链配置:确保链配置正确,处理程序按正确的顺序设置。

处理人员职责:每个处理人员应具有明确的职责,并且不应与其他处理人员的职责重叠。

默认处理:包括链中没有处理程序可以处理请求的情况的逻辑。

JavaScript 中的访问者模式

访问者模式是一种行为设计模式,它允许你将算法与其操作的对象结构分离。它提供了一种无需修改对象类即可向对象添加新操作的方法,从而轻松扩展复杂对象层次结构的功能。当你拥有一组不同的元素,并且希望在不修改其代码的情况下对它们执行各种操作时,此模式尤其有用。

在 JavaScript 中,你可以使用函数或类来实现访问者模式,这些函数或类表示访问对象结构中元素的访问者。让我们通过实际示例来探索如何在 JavaScript 中实现和使用访问者模式。

示例实现

假设您正在开发一个内容管理系统,其中包含不同类型的内容元素,例如文章、图片和视频。您希望在不修改这些元素类的情况下对这些元素执行各种操作,例如渲染和导出。您可以使用访问者模式:

// Element interface
class ContentElement {
  accept(visitor) {}
}

// Concrete Elements
class Article extends ContentElement {
  accept(visitor) {
    visitor.visitArticle(this);
  }
}

class Image extends ContentElement {
  accept(visitor) {
    visitor.visitImage(this);
  }
}

class Video extends ContentElement {
  accept(visitor) {
    visitor.visitVideo(this);
  }
}

// Visitor interface
class Visitor {
  visitArticle(article) {}
  visitImage(image) {}
  visitVideo(video) {}
}

// Concrete Visitors
class RendererVisitor extends Visitor {
  visitArticle(article) {
    console.log(`Rendering article: ${article.title}`);
  }

  visitImage(image) {
    console.log(`Rendering image: ${image.caption}`);
  }

  visitVideo(video) {
    console.log(`Rendering video: ${video.title}`);
  }
}

class ExportVisitor extends Visitor {
  visitArticle(article) {
    console.log(`Exporting article: ${article.title}`);
  }

  visitImage(image) {
    console.log(`Exporting image: ${image.caption}`);
  }

  visitVideo(video) {
    console.log(`Exporting video: ${video.title}`);
  }
}

// Usage
const elements = [new Article('Article 1'), new Image('Image 1'), new Video('Video 1')];
const renderer = new RendererVisitor();
const exporter = new ExportVisitor();

elements.forEach((element) => {
  element.accept(renderer);
  element.accept(exporter);
});
Enter fullscreen mode Exit fullscreen mode

在此示例中,我们有 Article、Image 和 Video 等内容元素,我们希望在不修改其类的情况下对它们执行渲染和导出操作。为此,我们通过实现 RendererVisitor 和 ExportVisitor 等访问者类来实现,这些类可以访问这些元素并执行所需的操作。

真实用例

访问者模式在各种场景中都很有价值,包括:

文档处理:处理文档中的元素,例如 HTML 或 XML,不同的访问者可以执行解析、渲染或转换操作。

编译器设计:在编译器中,访问者可以遍历和分析编程语言的抽象语法树(AST),以实现类型检查、优化和代码生成等各种目的。

数据结构:当处理树或图等复杂数据结构时,访问者可以遍历和操作数据的结构或内容。

报告和分析:在报告系统中,访问者可以生成报告、执行数据分析或从数据集中提取特定信息。

注意事项

使用访问者模式时,请考虑以下几点:

可扩展性:该模式通过创建新的访问者类而无需修改现有元素,可以轻松添加新操作。

复杂性:请注意,模式可能会引入额外的复杂性,尤其是对于简单的对象结构。

封装:确保元素正确封装其状态并通过访问者方法提供访问。

结论

在这篇全面探索 JavaScript 设计模式的文章中,我们深入探讨了各种模式,这些模式能够帮助开发者创建灵活、可维护且高效的代码。每种设计模式都针对特定的问题,并为常见的软件设计挑战提供了优雅的解决方案。

我们首先了解了设计模式的基本概念,并将其分为三大类:创建型模式、结构型模式和行为型模式。在每个类别中,我们都研究了流行的设计模式,并展示了它们在 JavaScript 中的实际实现。

以下是我们所讨论的关键设计模式的简要回顾:

  • 创建模式:这些模式专注于对象创建机制,包括用于确保类的单个实例的单例模式、用于使用灵活工厂创建对象的工厂和抽象工厂模式、用于逐步构建复杂对象的建造者模式、用于克隆对象的原型模式以及用于高效对象重用的对象池模式。

  • 结构模式:这些模式处理对象组合,提供从简单组件构建复杂结构的方法。我们探索了用于适配接口的适配器模式、用于动态添加对象行为的装饰器模式、用于控制对象访问的代理模式、用于将对象组合成树形结构的组合模式、用于分离抽象与实现的桥接模式,以及通过共享公共状态来最小化内存占用的享元模式。

  • 行为模式:这些模式关注对象之间的交互和通信。我们介绍了用于实现分布式事件处理系统的观察者模式、用于封装可互换算法的策略模式、用于将请求转换为独立对象的命令模式、用于基于内部状态管理对象行为的状态模式、用于构建处理请求的处理程序链的责任链模式,以及用于将算法与对象结构分离的访问者模式。

设计模式是开发者工具包中不可或缺的工具,能够帮助开发者创建可扩展且易于维护的代码库。通过理解这些模式并将其应用于 JavaScript 项目,您可以编写更高效、适应性更强、更强大的软件。

请记住,设计模式并非一刀切的解决方案,其适用性取决于项目的具体需求和挑战。请仔细考虑何时以及如何应用它们,以获得最佳效果。

随着你作为 JavaScript 开发者的不断成长,掌握这些设计模式将使你能够自信地、富有创造力地应对复杂的软件设计挑战。无论你构建的是 Web 应用程序、游戏引擎还是其他任何软件,设计模式都将成为你编写优雅且可维护代码的得力助手。祝你编程愉快!

文章来源:https://dev.to/alexmercedcoder/oop-design-patterns-in-javascript-3i98
PREV
Google 软件工程师级别:角色和期望(含薪资)
NEXT
深入探究 JavaScript 中的函数式编程