js设计模式系列篇:观察者模式

625 阅读2分钟

定义

观察者(observer)模式是js中应用很广泛的一种设计模式。它主要是通过在一个目标对象上维护一系列依附于它的观察者(observer)对象来实现,在目标对象上有任何状态改变时,会自动地通知给观察者。

先来看一个简单的实现

观察者的实现

class Observer {
    constructor () {}

    update (context) {
        //观察者收到通知后触发的函数
        console.log(`recieved data is ${context}`)
    }
}

以上是简单的观察者的定义。目标对象在需要的时候可以触发update函数以实现对观察者的通知。

观察者列表

//观察者列表
class ObserverList {
    constructor () {
        //用数组去保存观察者
        this.list = []
    }
    //添加一个观察者
    add (observer) {
        this.list.push(observer)
    }
    //获取观察者的个数
    count () {
        return this.list.length
    }
    //移除观察者
    remove (observer) {
        this.list = this.list.filter(item => item !== observer)
    }
    //获取观察者
    getIndexAt (index) {
        if (index > -1 && index < this.count()) {
            return this.list[index]
        }
    }
}

以上是观察者列表,list数组中保存的是一个个Observer类的实例,即观察者。我们可以用该类,对依附在某一个目标对象上的观察者进行统一的管理和维护。

目标对象

//目标对象
class Subject {
    constructor () {
        //观察者依附于目标对象上
        this.observers = new ObserverList()
    }
    //在目标对象上新增一个观察者
    addObserver (observer) {
        this.observers.add(observer)
    }
    //从目标对象上移除观察者
    removeObserver (observer) {
        this.observers.remove(observer)
    }
    //通知观察者
    notify (context) {
        const len = this.observers.count()
        for (let i = 0; i < len; i++) {
            this.observers.getIndexAt(i).update(context)
        }
    }
}

从以上代码可以看到,目标对象实例上挂载了一个ObserverList实例对象。用来保存依附在该目标对象上的所有观察者。当目标对象状态有变化时,可以通过notify函数,遍历触发所有观察者上的update函数,以此来实现对所有观察者的通知

代码实现完毕,我们现在来试验一下

//创建观察者
const observer1 = new Observer()
const observer2 = new Observer()
//创建一个目标对象
const subject = new Subject()
//将两个观察者加入到目标对象上的观察者列表中
subject.addObserver(observer1)
subject.addObserver(observer2)
//通知观察者
subject.notify('hello my observer')

运行以上代码,可以在控制台看到输出了两次,如下图

image.png

这就说明目标对象成功通知到了依附在自身的两个观察者

我们来试验一下移除观察者功能

//创建观察者
const observer1 = new Observer()
const observer2 = new Observer()
//创建一个目标对象
const subject = new Subject()
//将两个观察者加入到目标对象上的观察者列表中
subject.addObserver(observer1)
subject.addObserver(observer2)
//移除观察者
subject.removeObserver(observer1)
//通知观察者
subject.notify('hello my observer')

以上代码中,目标对象在通知观察者之前移除了其中一个观察者,所以我们可以看到控制台只打印了一次消息

image.png

小案例-画彩虹

在了解完具体的观察者模式之后,我们来实现一个小案例。最终的效果如下图

20211025-165418.gif

实现出来的效果是,按住下方的小黑圈左右拖动,上方的7个彩虹颜色的div的宽度也会相应地放大和缩小。

在本案例中,小黑圈就是目标对象,7个彩虹颜色地div就是依附在该小黑圈中的观察者。当小黑圈的坐标发生变化时,需要通知给这些观察者,告诉他们需要改变自身的宽度。

下面来看具体的实现

html结构

样式部分省略

<div class="rainbow" style="background-color: rgb(255,0,0);"></div>
<div class="rainbow" style="background-color: rgb(255,165,0);"></div>
<div class="rainbow" style="background-color: rgb(255,255,0);"></div>
<div class="rainbow" style="background-color: rgb(0,255,0);"></div>
<div class="rainbow" style="background-color: rgb(0,127,255);"></div>
<div class="rainbow" style="background-color: rgb(0,0,255);"></div>
<div class="rainbow" style="background-color: rgb(139,0,255);"></div>

<div id="progress-bar"></div>

定义observer

class Observer {
    //item是需要充当观察者的7个div元素
    constructor (item) {
        this.item = item
    }
    //观察者收到通知时,更新自身的width
    update (data) {
        this.item.style.width = data + 'px'
    }
}

定义观察者列表

//观察者列表,跟之前实现的差不多
class ObserverList {
    constructor () {
        this.list = []
    }

    add (observers) {
        this.list.push(observers)
    }

    count () {
        return this.list.length
    }

    getIndexAt (index) {
        return this.list[index]
    }
}

目标对象

//目标对象的实现也基本上一样
class Subject {
    constructor (subject) {
        this.observers = new ObserverList()
    }

    addObserver (observers) {
        this.observers.add(observers)
    }

    notify (data) {
        const len = this.observers.count()
        for (let i = 0; i < len; i++) {
            this.observers.getIndexAt(i).update(data)
        }
    }
}

之后我们要做的就是,获取7个div元素充当观察者,并添加到我们实例化的目标对象上。如下

const rainbows = document.querySelectorAll('.rainbow')
const bar = document.getElementById('progress-bar')
const barSubject = new Subject()
rainbows.forEach(item => {
    const observer = new Observer(item)
    barSubject.addObserver(observer)
})

在上面的代码中,我们获取了7个div对象,并遍历用每一个div实例化一个Observer,并添加到目标对象上。

之后要做的就是实现目标对象的拖放,并在拖放过程中通知观察者即可,实现拖放的部分省略,这里给出通知给观察者的部分,如下

document.onmousemove = function (event) {

    event = event || window.event;

    const sl = document.body.scrollLeft || document.documentElement.scrollLeft;

    const left = event.clientX - ol;
    box.style.left = left + sl + "px";
    //通知观察者
    barSubject.notify(left + sl)
}

到此,案例就完成了。希望对大家有所帮助。有不对的地方欢迎大佬们指正。

注意:观察者模式和大家熟知的发布/订阅者模式是有区别的,我会在下一篇文章中介绍