JS-设计模式 | 青训营

1. 单例模式

单例模式就是一个类只能创建单个对象(保证一个类,仅有一个实例),也就是说一对一,这个类是访问实例的唯一,也可直接访问,不需要实例化该类的对象。单例模式也属于一种创建型模式。

1.1 静态方法版

class Singleton {
  action() {
    console.log("单例对象");
  }
  static getInstance() {
    // 判断是否已经创建过1个实例
    if (!Singleton.instance) {
      // 若这个唯一的实例不存在,那么先创建它
      Singleton.instance = new Singleton();
    }
    // 如果实例已经存在,则直接返回
    return Singleton.instance;
  }
}

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();

// true
s1 === s2;

1.2 构造函数

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

s1 = new Singleton();
s2 = new Singleton();

// true
console.log(s1 === s2);

1.3 闭包 + IIFE

class Singleton {
  action() {
    console.log("单例对象");
  }
  static getInstance = (function () {
    let instance = null;
    return function () {
      if (!instance) {
        instance = new Singleton();
      }
      return instance;
    };
  })();
}

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();

console.log(s1 === s2);

2. 工厂模式

工厂模式其实就是将创建对象的过程单独封装

工厂模式会根据不同的输入返回所要求的实例,将对象的创建与实现分开来。用户提供参数,工厂负责返回相应的产品,其中的逻辑处理用户并不关心。

先构建一个人员的对象

const worker = {
    name: '123',
    age: 12,
    gender: 'male',
    // ......
}

上面的worker只能对应某一个人员,面对现实中属性各异的人员,一个一个创建显然不科学,也不难想到利用不同构造函数来创建实例。

function Coder(name, age) { //录入每个程序员的信息
    this.name = name;
    this.age = age;
    this.career = 'coder';
    this.work = ['写bug', '改bug', '996'];
}

function ProductManager(name, age) { //录入每个产品的信息
    this.name = name;
    this.age = age;
    this.career = 'product manager';
    this.work = ['开会', '画图', '监督'];
}

const coder1 = new Coder('test1', 19);
const manager1 = new ProductManager('test2', 32);

以上通过调用不同的构造函数来获取不同的实例,但可以通过一个统一的工厂函数来实现。

function Factory(name, age, career) {
    switch (career) {
        case 'coder':
            return new Coder(name, age);
            break;
        case 'product manager':
            return new ProductManager(name, age);
            break;
    }
}

const coder1 = new Factory('test1', 19, 'code');
const manager1 = new Factory('test2', 32, 'product manager');

继续改进,以上代码对不同类之间的共性整合太少,多种不同人员需要调用多种不同的构造函数来获取实例,我们对构造函数本身进行改进。

function User(name, age, career, work) { 
    this.name = name;
    this.age = age;
    this.career = career;
    this.work = work;
}

function Factory(name, age, career) {
    switch (career) {
        case 'coder':
            work = ['写bug', '改bug', '996'];
            break;
        case 'product manager':
            work = ['开会', '画图', '监督'];
            break;
    }
    return new User(name, age, career, work);
}

3. 观察者模式

观察者模式是一种对象间的一对多依赖关系,当一个对象(主题)的状态发生变化时,其所有依赖对象(观察者)都会得到通知并自动更新。

涉及到两方:观察者 和 被观察者

// 被观察者
class ObservedTarget {
  constructor() {
    this.observers = []; //用于存储所有的观察者
  }
  addObserver(...observer) {
    this.observers.push(...observer); //添加观察者
  }
  notifyObserver(...args) {
    // 遍历观察者列表,调用观察者的update方法
    this.observers.forEach((item) => {
      item.update(...args);
    });
  }
}
// 观察者
class Observer {
  constructor(name) {
    this.name = name;
  }
  update(...args) {
    let content = [...args];
    console.log(`${this.name}接收到目标对象更新的状态是:${content}`);
  }
}

在以上的代码中,定义了两个类,分别代表被观察者和观察者,被观察者维护一个观察者的列表,在有变化需要通知观察者时,调用notify方法,实质会遍历观察者列表,并调用其update方法

// 创建多个观察者
let observer1 = new Observer("observer1");
let observer2 = new Observer("observer2");
let observer3 = new Observer("observer3");

// 把观察者本身依附在目标对象上
let observerTarget = new ObservedTarget();
observerTarget.addObserver(observer1, observer2, observer3); //直接关联

// 当目标对象更新内容时,通知所有的观察者
observerTarget.notifyObserver("来自被观察者", "我已更新");

//observer1接收到目标对象更新的状态是:来自被观察者,我已更新
//observer2接收到目标对象更新的状态是:来自被观察者,我已更新
//observer3接收到目标对象更新的状态是:来自被观察者,我已更新

4. 发布订阅模式

发布订阅模式中,发布者与订阅者并不直接关联,而是通过一个中介进行互动,该中介被称为事件总线,订阅者向事件总线订阅感兴趣的内容,发布者向事件总线发布自己的内容,事件总线根据内容向已订阅的订阅者进行通知操作。

// 发布订阅
const eventHub = {
  map: {},
  // 订阅事件
  on: (event, fn) => {
    eventHub.map[event] = eventHub.map[event] || [];
    eventHub.map[event].push(fn);
  },
  // 取消订阅事件
  off: (event, fn) => {
    const q = eventHub.map[event];
    if (!q) return;
    // 此处targetFn用于处理once的回调函数
    const index = q.findIndex((item) => item === fn || item.targetFn === fn);
    if (index < 0) {
      return;
    }
    q.splice(index, 1);
  },
  // 发布事件
  emit: (event, ...args) => {
    const q = eventHub.map[event];
    if (!q) return;
    q.map((fn) => fn.call(null, ...args));
    return undefined;
  },
  // 订阅事件,回调只执行一次
  once: (event, fn) => {
    const on = (...args) => {
      eventHub.off(event, fn);
      fn.call(null, ...args);
    };
    // 用于在off时,判断是否为原有的回调函数
    on.targetFn = fn;
    eventHub.on(event, on);
  },
};

以上代码实现了一个 EventHub,主要包含以下几个方法:

  • on:用于订阅事件
  • off:用于取消订阅事件
  • once:用于订阅事件,但回调只执行一次
  • emit:用于发布事件
eventHub.on("click", console.log);
eventHub.on("click", console.error);
const logReverse = (s) => console.log(s.split("").reverse().join(""));
eventHub.once("click", logReverse);

setTimeout(() => {
  eventHub.emit("click", "123");
}, 3000);

setTimeout(() => {
  eventHub.emit("click", "456");
}, 4000);

运行结果如图:

image.png

可以看到 logReverse只执行了一次,满足需求。

全部评论

相关推荐

头顶尖尖的程序员:我是26届的不太懂,25届不应该是找的正式工作吗?为什么还在找实习?大四还实习的话是为了能转正的的岗位吗
点赞 评论 收藏
分享
07-08 13:48
门头沟学院 C++
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务