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

前言

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

单例模式

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

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

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

实现方法

闭包实现

js 复制代码
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 实例被创建和共享。

构造函数实现

js 复制代码
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. 插件和模块化开发:在插件和模块化开发中,发布-订阅模式可以提供一种灵活的扩展机制。插件可以通过订阅特定的事件来扩展应用程序的功能,而不需要直接修改应用程序的源代码,从而实现了插件和模块之间的解耦。

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

实现方法

js 复制代码
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

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

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

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

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

总结

单例模式:

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

发布-订阅模式:

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

相关推荐
wuhen_n几秒前
破冰——建立我们的AI开发实验环境
前端·javascript
HelloReader4 分钟前
Flutter 自适应布局一套代码适配手机和平板(十二)
前端
牛奶7 分钟前
HTTP裸奔,HTTPS穿盔甲——它们有什么区别?
前端·http·https
梓言9 分钟前
tailwindcss构建执行npm exec tailwindcss init -p 报错
前端
哈罗哈皮10 分钟前
龙虾(openclaw)本地快速安装及使用教程
前端·aigc·ai编程
用户231154445305811 分钟前
React中实现“双向绑定”效果的几种方式
前端
HelloReader12 分钟前
Flutter Sliver 高级滚动打造 iOS 通讯录体验(十三)
前端
大大花猫33 分钟前
求职简历的几个小建议
面试
a11177644 分钟前
程序化几何背景生成器(html 开源)
前端·开源·html
浮笙若有梦1 小时前
我开源了一个比 Ant Design Table 更好用的高性能虚拟表格
前端·vue.js