面试中js常问的12个设计模式

工厂模式

原理:

工厂模式通过使用工厂方法来创建对象,而不是直接使用new关键字。工厂方法根据输入参数的不同,决定创建哪个具体的对象实例,并将其返回。

代码实现:

javascript 复制代码
// 定义一个产品类
class Product {
  constructor(name) {
    this.name = name;
  }

  display() {
    console.log(`Product: ${this.name}`);
  }
}

// 定义工厂类
class Factory {
  createProduct(name) {
    return new Product(name);
  }
}

// 使用工厂创建对象
const factory = new Factory();
const product1 = factory.createProduct('Product 1');
const product2 = factory.createProduct('Product 2');

product1.display(); // 输出: Product: Product 1
product2.display(); // 输出: Product: Product 2

使用场景:

  • 当需要创建多个相似的对象时。
  • 当对象创建过程复杂或需要隐藏创建逻辑时。
  • 当希望通过一个公共的接口来创建对象时。

优点:

  • 将对象的创建与使用代码分离,客户端只需关注接口而不需要关心具体的对象创建过程。
  • 可以通过工厂方法来创建不同类型的对象,提供灵活性和可扩展性。

单例模式

原理:

单例模式确保一个类只有一个实例,并提供全局访问点以获取该实例。它通过私有化构造函数,限制外部直接创建对象,并提供一个静态方法来获取或创建唯一的实例。

代码实现:

ini 复制代码
class Singleton {
  static instance = null;

  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
  }
}

// 获取实例
const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // 输出: true

使用场景:

  • 当只需要一个全局对象来协调系统中的操作时。
  • 当需要频繁访问同一个对象实例时。
  • 当需要限制一个类只能有一个实例时。

优点:

  • 提供了对唯一实例的全局访问,方便共享对象。
  • 避免了重复创建实例的开销,节省了内存和资源

观察者模式

原理:

观察者模式定义了对象之间的一对多依赖关系。当一个对象的状态发生变化时,它的所有依赖者(观察者)都会被通知和更新。

代码实现:

javascript 复制代码
class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    this.observers = this.observers.filter((obs) => obs !== observer);
  }

  notifyObservers() {
    this.observers.forEach((observer) => observer.update());
  }
}

class Observer {
  constructor(name) {
    this.name = name;
  }

  update() {
    console.log(`Observer ${this.name} has been notified.`);
  }
}

// 创建主题和观察者
const subject = new Subject();
const observer1 = new Observer('1');
const observer2 = new Observer('2');

// 注册观察者
subject.addObserver(observer1);
subject.addObserver(observer2);

// 通知观察者
subject.notifyObservers();

使用场景:

  • 当一个对象的变化需要通知其他对象,以便它们可以做出相应的响应时。
  • 当对象之间的耦合度需要降低,使得它们可以独立地交互时。

优点

  • 实现了对象之间的松耦合,被观察者和观察者可以独立地演化和变化。
  • 可以轻松添加或移除观察者,以实现动态的发布-订阅机制。

发布-订阅模式

原理:

发布-订阅模式类似于观察者模式,但是发布者(或称为主题)不会直接通知特定的订阅者,而是通过消息代理(或称为事件总线)来分发和传递消息。订阅者可以根据自身的需求订阅感兴趣的消息。

代码实现:

javascript 复制代码
class EventBus {
  constructor() {
    this.subscribers = {};
  }

  subscribe(eventName, callback) {
    if (!this.subscribers[eventName]) {
      this.subscribers[eventName] = [];
    }
    this.subscribers[eventName].push(callback);
  }

  unsubscribe(eventName, callback) {
    if (this.subscribers[eventName]) {
      this.subscribers[eventName] = this.subscribers[eventName].filter(
        (cb) => cb !== callback
      );
    }
  }

  publish(eventName, data) {
    if (this.subscribers[eventName]) {
      this.subscribers[eventName].forEach((callback) => callback(data));
    }
  }
}

// 创建事件总线
const eventBus = new EventBus();

// 订阅事件
const callback1 = (data) => console.log(`Subscriber 1 received: ${data}`);
const callback2 = (data) => console.log(`Subscriber 2 received: ${data}`);
eventBus.subscribe('event1', callback1);
eventBus.subscribe('event1', callback2);

// 发布事件
eventBus.publish('event1', 'Hello, subscribers!');

使用场景:

  • 当一个对象的状态变化需要通知多个订阅者时。
  • 当需要将发布者和订阅者解耦,使它们可以独立地演化时。
  • 当希望在系统中引入中介层以提供更灵活的消息传递机制时。

优点

  • 解耦了发布者和订阅者,使它们可以独立地交互。
  • 提供了更灵活的消息传递机制,可以实现更复杂的事件处理逻辑。

原型模式

原理:

原型模式通过克隆现有对象来创建新对象,而不是依赖显式的实例化过程。每个对象都可以作为另一个对象的原型,新对象会继承原型对象的属性和方法。

代码实现:

javascript 复制代码
class Prototype {
  constructor(name) {
    this.name = name;
  }

  clone() {
    return Object.create(Object.getPrototypeOf(this));
  }
}

// 创建原型对象
const prototype = new Prototype('Prototype');

// 克隆对象
const clone1 = prototype.clone();
const clone2 = prototype.clone();

console.log(clone1.name); // 输出: Prototype
console.log(clone2.name); // 输出: Prototype

使用场景:

  • 当创建对象的过程比较昂贵或复杂时,而且新对象的创建与现有对象的状态无关时。
  • 当希望通过修改原型对象来影响所有克隆对象时。
  • 当需要避免使用new关键字直接实例化对象时。

优点

  • 避免了创建对象的昂贵或复杂过程,提高了性能和效率。
  • 可以通过修改原型对象来影响所有克隆对象,实现了对象状态的批量修改。

适配器模式

原理:

适配器模式将一个类的接口转换成另一个接口,以满足客户端的需求。它通过创建一个适配器类来实现接口转换,并在适配器类中调用被适配类的方法。

代码实现:

javascript 复制代码
class Adaptee {
  specificRequest() {
    return 'Specific request';
  }
}

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

  request() {
    return this.adaptee.specificRequest();
  }
}

// 使用适配器
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
console.log(adapter.request()); // 输出: Specific request

使用场景:

  • 当需要将一个已有类的接口转换成另一个接口时。
  • 当希望通过一个统一的接口来使用多个不兼容的类时。
  • 当需要在不影响现有代码的情况下,对已有类的方法进行扩展或修改时。

优点

  • 可以将已有类与新代码进行无缝衔接,使它们能够协同工作。
  • 可以实现对象之间的接口转换,提供了灵活性和可扩展性。

装饰者模式

原理:

装饰者模式动态地给对象添加新的行为或功能,同时不改变其原始类结构。它通过创建一个装饰器类来包装原始对象,并在装饰器类中添加额外的行为。

代码实现:

javascript 复制代码
class Component {
  operation() {
    return 'Component operation';
  }
}

class Decorator {
  constructor(component) {
    this.component = component;
  }

  operation() {
    return `${this.component.operation()} + Decorator operation`;
  }
}

// 使用装饰者
const component = new Component();
const decorator = new Decorator(component);
console.log(decorator.operation()); // 输出: Component operation + Decorator operation

使用场景:

  • 当需要在不改变现有对象结构的情况下,动态地给对象添加新的行为时。
  • 当希望通过透明的方式为对象添加功能,而不影响其使用方式和客户端代码时。
  • 当不适合使用子类来扩展对象功能时。

优点

  • 可以透明地扩展对象的功能,而不会影响客户端代码。
  • 允许通过装饰器类组合和嵌套多个装饰器,实现复杂的功能组合。

策略模式

原理:

策略模式定义了一系列算法,将它们封装成独立的可互换的策略对象,并使得客户端可以在运行时动态地选择使用不同的策略。客户端通过与策略对象进行交互来实现不同的行为。

代码实现:

scala 复制代码
class Strategy {
  execute() {
    // 策略执行的具体操作
  }
}

class ConcreteStrategy1 extends Strategy {
  execute() {
    console.log('Strategy 1');
  }
}

class ConcreteStrategy2 extends Strategy {
  execute() {
    console.log('Strategy 2');
  }
}

// 使用策略
const strategy1 = new ConcreteStrategy1();
const strategy2 = new ConcreteStrategy2();

strategy1.execute(); // 输出: Strategy 1
strategy2.execute(); // 输出: Strategy 2

使用场景:

  • 当需要在多个算法或行为之间进行动态选择时。
  • 当希望将算法的实现与使用它的客户端代码分离,以便它们可以独立地演化和修改时。
  • 当不希望使用大量的条件语句来处理不同的情况时。

优点

  • 实现了算法的封装和多态性,可以根据需要灵活地切换算法。
  • 将算法的实现与使用它的客户端代码分离,使得它们可以独立演化和修改。

模块模式

原理:

模块模式使用函数作用域和闭包来封装和组织代码,实现模块化和私有性。它通过返回一个包含公共方法和属性的对象,来实现对外部的封装。

代码实现:

javascript 复制代码
const module = (function() {
  let privateVariable = 'Private';

  function privateMethod() {
    console.log('Private method');
  }

  return {
    publicVariable: 'Public',
    publicMethod: function() {
      console.log('Public method');
    }
  };
})();

console.log(module.publicVariable); // 输出: Public
module.publicMethod(); // 输出: Public method

使用场景:

  • 当希望将相关的方法和属性封装在一个单独的对象中时。
  • 当希望限制对方法和属性的访问,并保持私有性时。
  • 当需要实现模块化,避免全局命名冲突和污染时。

优点

  • 将相关的方法和属性封装在一个单独的对象中,提供了组织和管理代码的方式。
  • 通过闭包实现了私有性,可以隐藏内部实现细节,防止外部访问和修改。

代理模式

原理:

代理模式为一个对象提供一个代理或占位符,并控制对其的访问。代理对象可以在访问被代理对象之前或之后添加额外的逻辑,如延迟加载、权限控制、缓存等。

代码实现:

javascript 复制代码
class RealSubject {
  request() {
    console.log('Real subject request');
  }
}

class Proxy {
  constructor(realSubject) {
    this.realSubject = realSubject;
  }

  request() {
    // 在调用真实对象方法之前或之后执行额外操作
    console.log('Proxy request');
    this.realSubject.request();
  }
}

// 使用代理
const realSubject = new RealSubject();
const proxy = new Proxy(realSubject);
proxy.request(); // 输出: Proxy request, Real subject request

使用场景:

  • 当需要在访问对象之前或之后执行额外操作时。
  • 当希望通过代理控制对对象的访问权限时。
  • 当需要延迟加载对象或实现缓存等功能时。

优点:

  • 可以在访问对象之前或之后执行额外操作,如延迟加载、权限控制、缓存等。
  • 提供了对真实对象的访问控制,可以限制对对象的直接访问。

迭代器模式

原理:

迭代器模式提供了一种访问集合对象元素的方式,而无需暴露集合的内部结构。它将迭代逻辑封装在迭代器对象中,客户端通过迭代器来遍历集合。

代码实现:

kotlin 复制代码
// 定义集合对象
class Collection {
  constructor() {
    this.items = [];
  }
  
  addItem(item) {
    this.items.push(item);
  }
  
  getIterator() {
    return new Iterator(this.items);
  }
}

// 定义迭代器对象
class Iterator {
  constructor(collection) {
    this.collection = collection;
    this.index = 0;
  }
  
  hasNext() {
    return this.index < this.collection.length;
  }
  
  next() {
    return this.collection[this.index++];
  }
}

// 使用迭代器遍历集合
const collection = new Collection();
collection.addItem("Item 1");
collection.addItem("Item 2");
collection.addItem("Item 3");

const iterator = collection.getIterator();
while (iterator.hasNext()) {
  console.log(iterator.next());
}

使用场景:

  • 当集合对象的内部结构可能经常变化时,使用迭代器可以减少对客户端代码的影响。
  • 当需要对集合对象进行不同类型的遍历时,迭代器提供了统一的接口,使得遍历逻辑更加灵活和可扩展。
  • 当需要在遍历过程中对集合元素进行筛选、过滤或转换时,可以通过迭代器来实现。

优点:

  • 将遍历集合的责任从客户端代码中抽离出来,简化了客户端代码。
  • 隐藏了集合的内部结构,提供了更好的封装性和安全性。
  • 支持不同类型的集合,提供了统一的迭代接口。

状态模式

原理:

状态模式允许对象在内部状态发生改变时改变其行为,看起来就像是对象类发生了改变。它将每个状态封装在一个独立的类中,并允许对象在不同状态之间切换。

代码实现:

scala 复制代码
// 定义状态接口
class State {
  handle(context) {
    // 默认实现
  }
}

// 定义具体状态类
class ConcreteStateA extends State {
  handle(context) {
    console.log("State A");
    context.setState(new ConcreteStateB());
  }
}

class ConcreteStateB extends State {
  handle(context) {
    console.log("State B");
    context.setState(new ConcreteStateA());
  }
}

// 定义上下文类
class Context {
  constructor() {
    this.state = new ConcreteStateA();
  }
  
  setState(state) {
    this.state = state;
  }
  
  request() {
    this.state.handle(this);
  }
}

// 使用状态模式
const context = new Context();
context.request();  // 输出 "State A"
context.request();  // 输出 "State B"
context.request();  // 输出 "State A"

使用场景:

  • 当一个对象的行为取决于其内部状态,并且在不同状态下具有不同行为时,可以使用状态模式来管理状态转换和行为。
  • 当需要在运行时根据条件动态地改变对象的行为时,状态模式提供了一种优雅的方式来实现。
  • 当对象有大量的条件语句,而且随着状态的增加会变得更加复杂时,可以使用状态模式来简化代码结构。

优点:

  • 将对象的状态和行为封装在独立的类中,提高了代码的可读性和可维护性。
  • 避免了使用大量的条件语句来处理不同的状态,简化了代码结构。
  • 新增或修改状态变得更加容易,不会对其他状态产生影响。
相关推荐
林涧泣8 分钟前
【Uniapp-Vue3】下拉刷新
前端·vue.js·uni-app
luoganttcc1 小时前
华为升腾算子开发(一) helloword
java·前端·华为
Ciderw2 小时前
MySQL为什么使用B+树?B+树和B树的区别
c++·后端·b树·mysql·面试·golang·b+树
九月十九2 小时前
AviatorScript用法
java·服务器·前端
翻晒时光2 小时前
深入解析Java集合框架:春招面试要点
java·开发语言·面试
_.Switch3 小时前
Python Web开发:使用FastAPI构建视频流媒体平台
开发语言·前端·python·微服务·架构·fastapi·媒体
菜鸟阿康学习编程3 小时前
JavaWeb 学习笔记 XML 和 Json 篇 | 020
xml·java·前端
索然无味io4 小时前
XML外部实体注入--漏洞利用
xml·前端·笔记·学习·web安全·网络安全·php
ThomasChan1234 小时前
Typescript 多个泛型参数详细解读
前端·javascript·vue.js·typescript·vue·reactjs·js
爱学习的狮王4 小时前
ubuntu18.04安装nvm管理本机node和npm
前端·npm·node.js·nvm