行为型:观察者模式

区别

虽然观察者模式经常又被叫做发布订阅模式,但是二者还是有区别的。

观察者模式是被观察者(Subject)直接通知所有的观察者(Observer)。被观察者和观察者之间是松耦合的。

observer-pattern

发布订阅模式中,发布者(Publisher)并不直接通知所有的订阅者(Subscriber),而是通过第三方的EventChannel。发布者和订阅者之间是完全解耦的。

publish-subscribe-pattern

实现

观察者模式

首先我们来实现Observer,这个类我们只需要一个update方法用来供Subject通知调用:

interface Observer {
  update(subject: Subject): void
}

class ObserverA implements Observer {
  public update(subject: Subject) {
    console.log('ObserverA updated')
  }
}

class ObserverB implements Observer {
  public update(subject: Subject) {
    console.log('ObserverB updated')
  }
}

接下来实现Subject,这个类需要实现add(添加观察者)remove(移除观察者)notify(通知观察者)三个方法,同时用observer私有属性来维护所有的观察者集合。

interface Subject {
  add(observer: Observer): void
  remove(observer: Observer): void
  notify(): void
}

class SubjectA implements Subject {
  private observers: Observer[]
  public constructor() {
    this.observers = []
  }
  public add(observer: Observer) {
    this.observers.push(observer)
  }
  public remove(observer: Observer) {
    const index = this.observers.findIndex(o => o === observer)
    this.observers.splice(index, 1)
  }
  public notify() {
    this.observers.forEach(observer => observer.update(this))
  }
}

playgroundopen in new window

我们注意到在notify()方法调用中调用遍历调用每一个observerupdate()方法时,会传递this,即把当前的subject实例传递过去。为什么需要这样做呢?因为通常Subject的业务类都会包含一些业务逻辑,才能实现我们实际业务中格中丰富的功能。

想像这样一个场景:小明(ObserverA小红(ObserverB分别喜欢阅读某个小说平台(StorySubject里的小说A小说B。小说平台里所有小说更新的时候都会给关注的用户推送私信,但是小明和小红都只关心自己喜欢的小说什么时候更新。

我们目前的实现,notify()调用时,会无差别调用所有observerupdate()方法。但是上面的场景中小明和小红都只关心各自喜欢的小说的更新情况。那么在StorySubject中引入额外的状态就可以通过状态判断来做到这一点了。我们来改造StorySubject

class StorySubject implements Subject {
  /**
   * 业务状态,0代表A小说更新了,1代表B小说更新了
   */
  public state = 0


  /**
   * 业务逻辑,模拟状态更新,通知观察者
   */
  public changeState() {
    this.state = Math.random() > 0.5 ? 1 : 0
    this.notify()
  }
}

改造Observer类:

class ObserverA implements Observer {
  public update(subject: Subject) {
    if (subject && subject instanceof StorySubject && subject.state === 1) {
      console.log('A小说更新了')
    }
  }
}

class ObserverB implements Observer {
  public update(subject: Subject) {
    if (subject && subject instanceof StorySubject && subject.state === 0) {
      console.log('B小说更新了')
    }
  }
}

通过对subject.state的判断,小明和和小红都能知道自己喜欢的小说何时更新了:

const observerA = new ObserverA() // 小明
const observerB = new ObserverB() // 小红
const storySubject = new StorySubject()
storySubject.add(observerA)
storySubject.add(observerB)
storySubject.changeState()  // A/B小说更新了(取决于当前state的值)

playgroundopen in new window

发布订阅模式

发布订阅模式需要实现三个类:PublisherSubscriberEventChannel

type Callback = (...args: unknown[]) => void

// 发布者
interface Publisher {
  eventName: string
  data: any
}

// 订阅者
interface Subscriber {
  eventName: string
  callback: Callback
}

// 事件总线
interface EventChannel {
  on: (eventName: string, callback: Callback) => void
  off: (eventName: string, callback: Callback) => void
  emit: (eventName: string, data: any) => void
}

分别实现它们:

class ConcretePublisher implements Publisher {
  public eventName: string
  public data: any
  constructor(eventName: string, data: any) {
    this.eventName = eventName
    this.data = data
  }
}

class ConcreteSubscriber implements Subscriber {
  public eventName: string
  public callback: Callback
  constructor(eventName: string, callback: Callback) {
    this.eventName = eventName
    this.callback = callback
  }
}

class ConcreteEventChannel implements EventChannel {
  private handlers: Map<string, Callback[]>

  constructor() {
    this.handlers = new Map()
  }

  public on(eventName: string, callback: Callback) {
    if (!this.handlers.has(eventName)) {
      this.handlers.set(eventName, [])
    }

    this.handlers.get(eventName)!.push(callback)
  }

  public off(eventName: string, callback: Callback) {
    const callbacks = this.handlers.get(eventName)
    if (callbacks) {
      const index = callbacks.indexOf(callback)
      if (index !== -1) {
        callbacks.splice(index, 1)
      }
    }
  }
  
  public emit (eventName: string, ...args: unknown[]) {
    const callbacks = this.handlers.get(eventName)
    if (callbacks) {
      callbacks.forEach((callback) => {
        callback(...args)
      })
    }
  }
}

用例:

const sub1 = new ConcreteSubscriber('ready', (...args) => {
  console.log('sub1接收到ready事件', args)
})

const sub2 = new ConcreteSubscriber('ready', (...args) => {
  console.log('sub2接收到ready事件', args)
})

const pub1 = new ConcretePublisher('ready', {
  message: 'pub1',
  data: 1
})

const pub2 = new ConcretePublisher('ready', {
  message: 'pub2',
  data: 2
})

const eventChannel = new ConcreteEventChannel()
eventChannel.on(sub1.eventName, sub1.callback)
eventChannel.on(sub2.eventName, sub2.callback)
eventChannel.emit(pub1.eventName, pub1.data)
eventChannel.off(sub2.eventName, sub2.callback)
eventChannel.emit(pub2.eventName, pub2.data)

/**
 * [LOG]: "sub1接收到ready事件",  [{"message": "pub1","data": 1}] 
 * [LOG]: "sub2接收到ready事件",  [{"message": "pub1","data": 1}] 
 * [LOG]: "sub1接收到ready事件",  [{"message": "pub2","data": 2}] 
 */

playgroundopen in new window

发布订阅模式的核心在于EventChannel,所以很多实现中可以不需要Publisher类和Subscriber类。比如上面的用例,如果只有EventChannel类,功能也是一样的:

const eventChannel = new ConcreteEventChannel()
function callback1(...args) {
  console.log('sub1接收到ready事件', args)
}
function callback2(...args) {
  console.log('sub2接收到ready事件', args)
}
eventChannel.on('ready', callback1)
eventChannel.on('ready', callback2)
eventChannel.emit('ready', {
  message: 'pub1',
  data: 1
})
eventChannel.off('ready', callback2)
eventChannel.emit('ready', {
  message: 'pub2',
  data: 2
})

比如我们常见的EventEmitter的实现就是这样,可以参考我的这篇文章open in new window

Last Updated: