观察者模式中通常有两个模型,一个观察者(observer
)和一个被观察者(Observed
)。从字面意思上理解,即被观察者发生某些行为或者变化时,会通知观察者,观察者根据此行为或者变化做出处理。那么具体如何操作呢,接下来我们就用JavaScript
代码实现一个下图👇所示的观察者模式。
let observer_ids=0; let observed_ids=0; //观察者类 class Observer { constructor() { this.id = observer_ids++; } //观测到变化后的处理 update(ob){ console.log("观察者" + this.id + `-检测到被观察者${ob.id}变化`); } } //被观察者列 class Observed { constructor() { this.observers = []; this.id=observed_ids++; } //添加观察者 addObserver(observer) { this.observers.push(observer); } //删除观察者 removeObserver(observer) { this.observers = this.observers.filter(o => { return o.id != observer.id; }); } //通知所有的观察者 notify(ob) { this.observers.forEach(observer => { observer.update(ob); }); } } let mObserved=new Observed(); let mObserver1=new Observer(); let mObserver2=new Observer(); mObserved.addObserver(mObserver1); mObserved.addObserver(mObserver2); mObserved.notify(); 复制代码
输出结果为
观察者0-检测到被观察者0变化
观察者1-检测到被观察者0变化
我们把执行代码修改,添加删除观察者的代码,
let mObserved=new Observed(); let mObserver1=new Observer(); let mObserver2=new Observer(); let mObserver3=new Observer(); mObserved.addObserver(mObserver1); mObserved.addObserver(mObserver2); mObserved.addObserver(mObserver3); mObserved.removeObserver(mObserver2); mObserved.notify(); 复制代码
输出结果为
观察者0-检测到被观察者0变化
观察者2-检测到被观察者0变化
我们可以不直接使用上面的两个类,而是把观察者和被观察者这两个类作为基类供其他类实现。
class Teacher extends Observer{ constructor(name){ super(); this.name=name; } update(st){ // super.update(st); console.log(st.name+`提交了${this.name}作业`); } } class Student extends Observed{ constructor(name){ super(); this.name=name; } submitHomeWork(){ this.notify(this) } } let teacher1=new Teacher("数学"); let teacher2=new Teacher("语文"); let stu1=new Student("小玲"); let stu2=new Student("小明"); let stu3=new Student("小李"); stu1.addObserver(teacher1); stu1.addObserver(teacher2); stu2.addObserver(teacher1); stu2.addObserver(teacher2); stu3.addObserver(teacher1); stu3.addObserver(teacher2); stu1.submitHomeWork(); stu2.submitHomeWork(); stu3.submitHomeWork(); 复制代码
上述代码的输出结果为
小玲提交了数学作业
小玲提交了语文作业
小明提交了数学作业
小明提交了语文作业
小李提交了数学作业
小李提交了语文作业
观察者模式通常也被称为发布/订阅模式,这时候被观察者作为发布者,观察者被称为订阅者。这个也很容易理解,我们以订阅微信公众号为例。我可以订阅很多个微信公众号,这时候我是订阅者,而微信公众号为发布者,当有微信公众号发布新的文章,公众号平台会通知我,接到通知就可以去阅读新文章了。
注意:上面关于发布订阅者模式的描述并不正确,下面进行更正。
发布订阅者模式与观察者模式类似,但是两者并不完全相同,发布订阅者模式与观察者相比多了一个中间层的调度中心,用来对发布者发布的信息进行处理再发布到订阅者,大致过程如下图所示。
那么问题来了,为什么要加一个中间成的调度中心呢?通过我们上面👆对观察者模式的实现,我们的observed
类中是持有observer
对象的,因此并没有实现两个类的完全解耦。通过添加中间层的调度中心类,我么可以将订阅者和发布者完全解耦,两者不再有直接的关联,而是通过调度中心关联起来。下面我们继续实现一个发布订阅者模式。
//发布者 class Pub{ constructor(dispatcher){ this.dispatcher=dispatcher; this.id=observed_ids++; } /** * @description: 发布方法 * @param {type} 通知类型 */ publish(type){ this.dispatcher.publish(type,this) } } //订阅者 class Subscriber{ constructor(dispatcher){ this.dispatcher=dispatcher; this.id=observer_ids++; } subscribe(type){ this.dispatcher.subscribe(type,this); } doUpdate(type,arg){ console.log("接受到消息"+arg) } } //调度中心 class Dispatcher{ constructor(){ this.dispatcher={}; } //订阅 subscribe(type,subscriber){ if(!this.dispatcher[type]){ this.dispatcher[type]=[]; } this.dispatcher[type].push(subscriber); } //退订 unsubscribe(type, subscriber) { let subscribers = this.dispatcher[type]; if (!subscribers || !subscribers.length) return; this.dispatcher[type] = subscribers.filter(item =>{ return item.id !== subscriber.id }); } //发布 publish(type, args) { let subscribers = this.dispatcher[type]; if (!subscribers || !subscribers.length) return; subscribers.forEach(subscriber=>{ subscriber.doUpdate(type,args); }); } } class Reader extends Subscriber{ constructor(name,dispatcher){ super(dispatcher); this.name=name; } doUpdate(type,st){ // super.update(st); console.log(this.name+`阅读了--${type}--公众号的文章`); } } class WeiX extends Pub{ constructor(name,dispatcher){ super(dispatcher); this.name=name; } publishArticle(type){ this.publish(type) } } let dispatcher=new Dispatcher(); //公众号 let wei1=new WeiX("前端",dispatcher); let wei2=new WeiX("数据库",dispatcher); //读者们 let reader1=new Reader("小玲",dispatcher); let reader2=new Reader("小明",dispatcher); let reader3=new Reader("小李",dispatcher); //读者订阅公众号 reader1.subscribe("前端"); reader2.subscribe("数据库"); reader3.subscribe("数据库"); //公众号发布文章 wei1.publishArticle("前端"); wei1.publishArticle("数据库"); 复制代码
运行结果如下:
小玲阅读了--前端--公众号的文章
小明阅读了--数据库--公众号的文章
小李阅读了--数据库--公众号的文章
以上是自己在阅读了《设计模式之禅》相关章节后自己对观察者模式的理解,如有不当之处还望!
感谢fengwei对文中我对观察者模式和发布订阅者模式描述错误的指正!