面试官:能介绍一下你对发布订阅和单例模式的理解吗

发布于:2024-05-01 ⋅ 阅读:(29) ⋅ 点赞:(0)

前言

我们都知道,设计模式,是在软件设计开发过程中,针对特定问题或场景较优解决方案。它可以帮助我们遇到相似的问题、场景时,能够快速找到更优的方式解决。而在日常面试中我们常遇见的就是:单例模式和发布订阅模式了。

单例模式

首先让我们来了解一下单例模式,单例模式是一种设计模式,用于确保类只有一个实例,并提供了全局访问点。这意味着在应用程序的生命周期内,无论何时需要该类的实例,都只会返回同一个实例。在我的日常使用中,需要用到单例模式的时候最常见的就是 vuex 了。单例模式通常在以下情况下被使用:

  1. 资源共享:当多个组件需要访问相同的资源时,单例模式可以确保只有一个实例被创建和共享,避免资源的重复创建和浪费。
  2. 全局状态管理:在需要全局状态管理的应用程序中,单例模式可以提供一个统一的访问点来管理应用程序的状态,使得状态的修改和访问更加方便和可控。
  3. 配置对象:当应用程序需要一个全局配置对象时,单例模式可以确保只有一个配置对象被创建,并且可以在整个应用程序中被访问和修改。
  4. 日志记录器:在需要记录应用程序日志的情况下,单例模式可以确保只有一个日志记录器被创建,并且可以在整个应用程序中被访问,以便统一管理日志输出。
  5. 数据库连接池:在需要管理数据库连接的应用程序中,单例模式可以确保只有一个数据库连接池被创建,并且可以在整个应用程序中被共享和复用,提高数据库访问的效率和性能。

那么我们要如何来实现一个单例模式呢?我们可以通过使用闭包和构造函数来实现单例模式。

实现方法

闭包实现

function Storage() {
  this.name = '张三';
}

const Helper = (function () {
  let instance = null;
  return function () {
    if (!instance) {
      instance = new Storage();
    }
    return instance;
  };
})();

let p1 = Helper();
let p2 = Helper();
console.log(p1 === p2); // 输出 true,说明只有一个实例被创建

在以上代码中,我们通过构造了一个 Helper 函数返回一个闭包,当我们需要创建实例对象时,就可以调用这个函数,因为我们在函数内部定义了 instane 作为标记,当第一次创建实例时,就会创建一个新的 Storage 实例赋给 instance ,然后返回这个 instance ;当已经创建了实例时,instance 的值将不会是 null 了,因此他也就不会再创建一个新的实例了。这样可以确保只有一个 Storage 实例被创建和共享。

构造函数实现

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

  static getInfo(name) {
    if (!this.instance) {
      this.instance = new Person(name);
    }
    return this.instance;
  }
}

let p1 = Person.getInfo('张三');
let p2 = Person.getInfo('李四');

console.log(p1 === p2); // 输出 true,说明只有一个实例被创建

在以上代码中,我们在 Person 函数中创建了一个静态方法 getInfo ,在 getInfo中如果在之前我们没有创建一个实例的话,那这个构造函数身上也就不会有 instance 这个属性,如果有的话我们只需要直接返回这个属性就行了。这样,无论调用 getInfo 多少次,都只会返回同一个实例,实现了单例模式的效果。

发布订阅

首先让我们来了解一下发布订阅是什么,发布订阅模式就是用于实现对象之间一种一对多的依赖关系,其中只存在一个发布者,但是可以有多个订阅者。发布者负责发布事件,而订阅者则订阅这些事件,并在事件发生时被通知到。也就是说,当对象身上发生改变时,其身上所依赖的对象也要相应的发生改变。发布-订阅模式通常在以下情况下被使用到:

  1. 松耦合的组件通信:当需要实现松耦合的组件通信时,发布-订阅模式非常有用。组件之间不需要直接引用彼此,而是通过事件总线进行通信,从而降低了它们之间的依赖关系,提高了组件的可复用性和可维护性。
  2. 跨组件通信:当需要在多个组件之间进行通信时,发布-订阅模式可以帮助实现跨组件的通信。通过订阅感兴趣的事件,并在事件发生时执行相应的操作,可以方便地实现组件之间的数据传递和状态同步。
  3. 解耦异步操作:当需要解耦异步操作时,发布-订阅模式可以提供一种方便的方式来处理异步事件。例如,可以订阅异步操作的成功或失败事件,并在事件发生时执行相应的操作,而不需要在异步操作的回调函数中直接处理业务逻辑。
  4. 全局事件管理:当需要管理全局事件时,发布-订阅模式可以帮助实现全局事件的管理和分发。通过使用一个事件总线来管理所有的事件,并在需要时发布和订阅事件,可以方便地实现全局事件的处理和分发。
  5. 插件和模块化开发:在插件和模块化开发中,发布-订阅模式可以提供一种灵活的扩展机制。插件可以通过订阅特定的事件来扩展应用程序的功能,而不需要直接修改应用程序的源代码,从而实现了插件和模块之间的解耦。

通常来说,我们是使用事件总线来实现发布订阅者模式的。事件总线在这个过程中充当着中介,负责管理事件的发布和订阅。下面我来带大家实现一个发布订阅模式。

实现方法

class EventEmitter {
  constructor() {
    this.event = {};
  }

  on(Event, fn) {
    if (!this.event[Event]) {
      this.event[Event] = [];
    }
    this.event[Event].push(fn);
  }

  emit(Event) {
    this.event[Event].forEach((fn) => {
      fn();
    });
  }

  off(Event, fn) {
    console.log(this.event[Event]);
    if (this.event[Event].indexOf(fn) !== -1) {
      this.event[Event].splice(this.event[Event].indexOf(fn), 1);
    }
  }
}

let emiter = new EventEmitter();

function A() {
  console.log("A");
}

emiter.on("click", A);

emiter.off("click", A);

function B() {
  console.log("B");
}
emiter.on("click", B);

emiter.emit("click");

在以上代码中,因为我们需要一个事件总线来充当中介,因此定义了 EventEmitter 函数,为了应对有多个事件的情况,我们定义了一个对象 event 来存储这些事件,并使用对象名 eventName 来作为key,因为一对多的关系,所以需要将其设置为数组类型。

然后它有三个方法:onemitoff

-   `on(Event, fn)`:订阅事件,当事件被触发时执行指定的回调函数。
-   `emit(Event)`:触发事件,执行该事件对应的所有回调函数。
-   `off(Event, fn)`:取消订阅事件,从事件的回调函数列表中移除指定的回调函数。
  1. 订阅事件:在调用 on 方法时,会将事件名和对应的回调函数存储在 event 对象中。如果之前没有该事件名对应的回调函数列表,则创建一个新的数组,并将回调函数存储在其中。

  2. 发布事件:在调用 emit 方法时,会根据事件名找到对应的回调函数列表,并依次执行其中的每个回调函数。

  3. 取消订阅事件:在调用 off 方法时,首先判断是否存在该事件名对应的回调函数列表,如果存在,则在列表中找到指定的回调函数,并将其从列表中移除。

这样我们就实现了一个简单的发布订阅模式了。

总结

单例模式:

单例模式是一种常见的设计模式,它能够确保一个类只有一个实例,并提供了全局访问点。在实际应用中,单例模式特别适用于需要管理全局状态或资源的场景。通过单例模式,我们可以避免资源的重复创建和浪费,同时提供一个统一的访问点来管理应用程序的状态。因此在日常开发中,我经常会使用单例模式来管理全局的配置对象、日志记录器或数据库连接池等资源,以提高代码的效率和性能。

发布-订阅模式:

发布-订阅模式是一种常见的设计模式,它用于实现对象之间的一对多的依赖关系。通过发布-订阅模式,我们可以实现松耦合的组件通信、跨组件通信以及解耦异步操作等功能。在实际应用中,发布-订阅模式特别适用于需要实现组件之间的解耦或跨组件通信的场景。通过订阅感兴趣的事件,并在事件发生时执行相应的操作,我们可以方便地实现组件之间的数据传递和状态同步,从而提高代码的可维护性和可扩展性。


网站公告

今日签到

点亮在社区的每一天
去签到