详解"观察者"模式和"发布订阅"模式

1,049 阅读6分钟

观察者模式和发布订阅模式都是常见的软件设计模式,用于解决组件间的通信或事件处理问题。它们的目标都是在组件之间建立一种松耦合的关系,使得组件之间的依赖关系更加灵活和可扩展。

概念定义

观察者模式

观察者模式是一种对象行为型模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象(或称为可观察对象),当主题对象的状态发生变化时,所有依赖于它的观察者对象都会得到通知并自动更新。观察者模式通常由以下几个角色组成:

  1. Subject(主题):也称为被观察者或可观察对象,它维护一系列观察者对象,并提供添加、删除和通知观察者的接口。
  2. Observer(观察者):观察主题的对象,当主题状态发生变化时会接收到通知,并执行相应的更新操作。
  3. ConcreteSubject(具体主题):具体的主题实现,维护主题状态,当状态发生改变时通知所有观察者。
  4. ConcreteObserver(具体观察者):具体的观察者实现,实现更新接口,以便在接收到通知时执行更新操作。

观察者模式在实际开发中常用于事件处理、UI界面更新等场景。

发布订阅模式

发布订阅模式也是一种对象行为型模式,它定义了一种一对多的依赖关系,但与观察者模式不同,发布订阅模式使用一个称为消息中心(或事件总线)的中间件来管理消息的发布和订阅。发布者(发布消息的对象)不需要直接与订阅者(接收消息的对象)进行通信,而是通过消息中心来发送和接收消息。发布订阅模式通常由以下几个角色组成:

  1. Publisher(发布者):发布消息的对象,当某个事件发生时,将消息发布到消息中心。
  2. Subscriber(订阅者):订阅消息的对象,通过向消息中心注册订阅事件,以便接收感兴趣的消息。
  3. Message Center(消息中心):负责管理消息的发布和订阅,它维护订阅者列表,并在消息发布时通知所有相关订阅者。

发布订阅模式在实际开发中常用于跨组件的通信、事件监听等场景。

概念总结

  1. 观察者模式通过观察者直接订阅主题,实现了主题和观察者之间的直接联系,主题状态改变时会直接通知观察者。
  2. 发布订阅模式通过消息中心充当中介,实现了发布者和订阅者之间的解耦,发布者和订阅者无需直接交互,而是通过消息中心来进行通信。
  3. 观察者模式更加简单,直接,适用于一对多的触发机制。
  4. 发布订阅模式更加灵活,适用于多对多的发布和订阅场景,可以实现更复杂的消息交互。

概念图解

如果单单看上述定义,可能不好理解这两个概念,下面我用图解的方式,形象的解释一下这两个概念

观察者模式图解

观察者模式实际上就是一个一对多的关系,在观察者模式中存在一个主题和多个观察者,主题也是被观察者,当我们主题发布消息时,会通知各个观察者,观察者将会收到最新消息,图解如下:每个观察者首先订阅主题,订阅成功后当主题发送消息时会循环整个观察者列表,逐一发送消息通知。

发布订阅模式图解

发布订阅模式和观察者模式的区别就是发布者和订阅者完全解耦,通过中间的发布订阅中心进行消息通知,发布者并不知道自己发布的消息会通知给谁,因此发布订阅模式有三个重要角色,发布者->发布订阅中心->订阅者。图解如下:当发布者发布消息到发布订阅中心后,发布订阅中心会将消息通知给所有订阅该发布者的订阅者

JavaScript代码实现两种模式

我们就根据上述概念和图解,在js中实现这两种模式

观察者模式代码实现

创建主题类(消息中心)

在消息中心类中有以下方法和属性:

  1. observers:observers数组用于存放所有观察者的实例对象
  2. addObserver:用于添加所有观察者到observers数组中去
  3. removeObserver:将观察者从observers数组中移除
  4. notify:通知方法,在方法中遍历所有观察者,并调用观察者的update方法,将消息通知给各个观察者
// 定义一个消息通知中心
class MessageCenter {
  constructor() {
    this.observers = []
  }

  // 添加订阅者
  addObserver(observer) {
    this.observers.push(observer)
  }

  // 删除订阅者
  removeObserver(observer) {
    this.observers = this.observers.filter(item => item !== observer)
  }

  // 通知所有订阅者
  notify(message) {
    this.observers.forEach(observer => {
      observer.update(message)
    })
  }
}

创建观察者类(消息接收者)

在观察者类中有以下属性和方法:

  1. name:用于定义观察者的姓名(这里只定义了姓名,还可以有任何有用的信息,例如id等)
  2. update:update方法用于接收消息中心发送的消息
// 定义一个消息观察者
class Observer {
  constructor(name) {
    this.name = name
  }

  update(message) {
    console.log(`${this.name} get message: ${message}`)
  }
}

创建实例并调用方法

const messageCenter = new MessageCenter()
const observer1 = new Observer('observer1')
const observer2 = new Observer('observer2')
messageCenter.addObserver(observer1)
messageCenter.addObserver(observer2)
messageCenter.notify('hello world')
messageCenter.removeObserver(observer1)
messageCenter.notify('hello world again')

上述调用将打印出如下内容:
image.png
此时我们就使用js实现了一个观察者模式的基本案例

发布订阅模式代码实现

我们以博客的发布订阅为例实现发布订阅模式

创建发布订阅中心类(博客中心)

博客发布订阅中心类有以下属性和方法:

  1. bloggerSubscribe:bloggerSubscribe数组用于存放博主和订阅者之间的关系,其中key为博主的唯一标识,这里使用name,value是个数组,存放所有订阅者的实例对象
  2. blogCenterSubscribe:blogCenterSubscribe方法用于订阅者订阅博主,并将博主与订阅者对应关系存放到bloggerSubscribe中
  3. blogCenterUnsubscribe:blogCenterUnsubscribe方法用于订阅者取消订阅博主,当该博主订阅者全部取消订阅时,可以将该博主移除。
  4. blogCenterPublishBlog:blogCenterPublishBlog方法用于将博主发布的博客推送给该博主的各个订阅者
// 定义博客发布订阅中心
class BlogCenter {
    constructor() {
        // 保存博主和订阅者对应关系的对象
        this.bloggerSubscribe = {}
    }

    // 订阅者订阅博主
    blogCenterSubscribe(subscriber, bloggerName) {
        if (!this.bloggerSubscribe[bloggerName]) {
            this.bloggerSubscribe[bloggerName] = []
        }
        if (!this.bloggerSubscribe[bloggerName].includes(subscriber)) {
            this.bloggerSubscribe[bloggerName].push(subscriber)
        }
    }

    // 订阅者取消订阅博主
    blogCenterUnsubscribe(subscriber, bloggerName) {
        if (this.bloggerSubscribe[bloggerName] && this.bloggerSubscribe[bloggerName].length > 0) {
            this.bloggerSubscribe[bloggerName] = this.bloggerSubscribe[bloggerName].filter(item => item !== subscriber)
        }
        if (this.bloggerSubscribe[bloggerName] && this.bloggerSubscribe[bloggerName].length === 0) {
            delete this.bloggerSubscribe[bloggerName]
        }
    }

    // 博主发布博客
    blogCenterPublishBlog(bloggerName, blog) {
        if (this.bloggerSubscribe[bloggerName] && this.bloggerSubscribe[bloggerName].length > 0) {
            this.bloggerSubscribe[bloggerName].forEach(subscriber => {
                subscriber.update(blog)
            })
        } else {
            console.log(`no subscriber for blogger: ${bloggerName}`)
        }
    }
}

创建博主类

博主类有以下属性和方法:

  1. name:博主的名字,这里作为博主的唯一标识
  2. publishBlog:publishBlog是发布博客的方法,这个方法接收两个参数,一个是实例化博客中心得实例对象,另一个就是需要发布的博客。博客中心的实例化对象会调用自己的blogCenterPublishBlog方法,将博客推送给各个订阅者
// 定义博主
class Blogger {
    constructor(name) {
        this.name = name
    }

    // 博主发布博客
    publishBlog(blogCenter, blog) {
        blogCenter.blogCenterPublishBlog(this.name, blog)
    }
}

创建订阅者类

订阅者类有以下属性和方法:

  1. name:用于定义订阅者的姓名
  2. subscribe:subscribe用于订阅博主,将自身实例对象和博主名字通过blogCenter的blogCenterSubscribe方法进行订阅
  3. unsubscribe:与subscribe方法类似
  4. update:update方法用于接收发布订阅中心发布的消息
class Subscriber {
    constructor(name) {
        this.name = name
    }

    // 订阅者订阅博主
    subscribe(blogCenter, bloggerName) {
        blogCenter.blogCenterSubscribe(this, bloggerName)
    }

    // 订阅者取消订阅博主
    unsubscribe(blogCenter, bloggerName) {
        blogCenter.blogCenterUnsubscribe(this, bloggerName)
    }

    // 订阅者接收博客
    update(blog) {
        console.log(`${this.name} receive blog: ${blog}`)
    }
}

创建实例并调用方法

// 创建博客发布订阅中心
const blogCenter = new BlogCenter()
// 创建博主
const blogger1 = new Blogger('blogger1')
const blogger2 = new Blogger('blogger2')
// 创建订阅者
const subscriber1 = new Subscriber('subscriber1')
const subscriber2 = new Subscriber('subscriber2')
const subscriber3 = new Subscriber('subscriber3')
// 订阅者订阅博主
subscriber1.subscribe(blogCenter, blogger1.name)
subscriber2.subscribe(blogCenter, blogger1.name)
subscriber3.subscribe(blogCenter, blogger2.name)
// 博主发布博客
blogger1.publishBlog(blogCenter, 'hello world')
blogger2.publishBlog(blogCenter, 'hello world again')
// 订阅者取消订阅博主
subscriber1.unsubscribe(blogCenter, blogger1.name)
// 博主再次发布博客
blogger1.publishBlog(blogCenter, 'hello world again again')
blogger2.publishBlog(blogCenter, 'hello world again again again')

运行结果打印如下:
image.png
此时我们就使用js实现了一个发布订阅模式的基本案例

案例补充

我们这里在使用发布订阅模式实现一个项目中常用得全局事件的方法,下面是具体代码,代码就不做解析了

// 使用发布订阅模式实现事件的监听和触发
// 1. 定义一个对象,用来存储事件
// 2. 定义一个on方法,用来监听事件
// 3. 定义一个emit方法,用来触发事件
// 4. 定义一个off方法,用来移除事件
// 5. 定义一个once方法,用来只监听一次事件
class EventEmitter {
    constructor() {
        this.events = {}
    }
    // 监听事件
    on(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = []
        }
        if (!this.events[eventName].includes(callback)) {
            this.events[eventName].push(callback)
        }
    }
    // 取消监听事件
    off(eventName, callback) {
        if (this.events[eventName]) {
            this.events[eventName] = this.events[eventName].filter(item => item !== callback)
        }
    }
    // 触发事件
    emit(eventName, arg) {
        if (this.events[eventName] && this.events[eventName].length > 0) {
            this.events[eventName].forEach(callback => {
                callback(arg)
            })
        } else {
            console.log(`no listener for event: ${eventName}`)
        }
    }
    // 只监听一次事件
    once(eventName, callback) {
        const fn = (arg) => {
            callback(arg)
            this.off(eventName, fn)
        }
        this.on(eventName, fn)
    }
}

// 创建一个事件监听器
const eventEmitter = new EventEmitter()
// 定义一个事件
const eventName = 'click'
// 定义一个事件监听器
const listener = (args) => {
    console.log(`receive event: ${args}`)
}
// 监听事件
eventEmitter.on(eventName, listener)
// 触发事件
eventEmitter.emit(eventName, 'hello world')
eventEmitter.emit(eventName, 'hello world again')
// 取消监听事件
eventEmitter.off(eventName, listener)
eventEmitter.emit(eventName, 'hello world again again')
// 只监听一次事件
eventEmitter.once(eventName, listener)
eventEmitter.emit(eventName, 'hello world again again again')
eventEmitter.emit(eventName, 'hello world again again again')

运行结果:
image.png