设计模式之观察者模式

0x01.定义与类型定义:定义了对象之间的一对多依赖,让多个观察者对象同时监听某一个主题对象,当主题对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新。类型:行为型UML类图 示例代码实现/** * 被观察实例抽象定义 */public abstract class Subject { /** * 观察者数组 */ protected List<Observer> observers; /** * 添加一个观察者 * @param observer */ public abstract void add(Observer observer); /** * 删除一个观察者 * @param observer */ public abstract void remove(Observer observer); /** * 通知观察者 */ public abstract void notifyObserver();}/** * 观察者接口 */public interface Observer { void response();}/** * 被观察的实例 */public class ConcreteSubject extends Subject { public ConcreteSubject() { super.observers = new ArrayList<>(); } @Override public void add(Observer observer) { this.observers.add(observer); } @Override public void remove(Observer observer) { this.observers.remove(observer); } @Override public void notifyObserver() { for (Observer observer : this.observers) { observer.response(); } }}/** * 观察者1 */public class ConcreteObserver1 implements Observer { @Override public void response() { System.out.println("通知观察者1"); }}/** * 观察者2 */public class ConcreteObserver2 implements Observer { @Override public void response() { System.out.println("通知观察者2"); }}测试与应用/** * 测试与应用 */public class Test { public static void main(String[] args) { //创建实例 Subject subject = new ConcreteSubject(); //创建观察者对象 Observer observer1 = new ConcreteObserver1(); Observer observer2 = new ConcreteObserver2(); //添加观察者对象 subject.add(observer1); subject.add(observer2); //通知观察者 subject.notifyObserver(); }}输出结果通知观察者1通知观察者2角色介绍: ...

November 2, 2019 · 2 min · jiezi

观察者模式从公众号群发说起

每个人应该都订阅了不少微信公众号,那你有没有注意到微信公众号的消息呢?你订阅的公众号号主每发布一篇文章,你都会主动的接收到文章的推送,并不需要你点开每个订阅的公众号一一查看有没有更新,是不是觉得有点意思?感兴趣?那就接着往下看吧,因为接下来我们要模拟公众号群发的场景。 要模拟公众号群发,首先需要简单的了解一下公众号的特点,对于公众号的特点,我总结了以下三点: 每个公众号会有多名订阅者,公众号跟订阅者在某种层面上是一对多的关系只有订阅者才能在公众号发布新文章时,会及时接收到推送通知,没有订阅公众号的阅读者不会接收到文章推送通知。每个订阅者都依赖于公众号号主,只有公众号号主发布文章,订阅者才有文章查看现在业务场景我们大概知道了,那就开始动手编写我们的业务吧,我们先从公众号号主开始。 对于公众号号主,我们先理解一下公众号特点的第二点:只有订阅者才能在公众号发布新文章时,会及时接收到推送通知,没有订阅公众号的阅读者不会接收到文章推送通知。这个特点说明在公众号号主这边维护者订阅者的列表,在每次发布文章时会通知列表中的每一个订阅者告诉他们有新文章了。如果号主没有订阅者列表,那怎么知道需要通知哪些人呢?对于这个订阅者列表,号主肯定有增删的权利,毕竟这个公众号你说了算。根据上面分析的,我们给号主做出一个抽象,我们建立一个抽象的Author类。Author类具体设计如下: /** * 号主抽象类 */public interface Author { // 添加关注者 void addReader(Reader reader); // 删除关注者 void deleteReader(Reader reader); // 通知关注者 void notifyReader(); // 写文章 void writeArticle(String article);}在我们的场景中号主主要有添加订阅者、删除订阅者、通知订阅者和写文章的功能,号主有了,接下来就是我们的订阅者,订阅者在我们的场景中就比较简单了,只有一个阅读的功能,我们定义一个阅读者抽象类Reader,Reader类的具体设计如下: /** * 阅读者接口 */public interface Reader { // 阅读文章 void reader(String authorName,String article);}号主和阅读者的接口都定义好了,下面我们就需要真正的号主和订阅者了。我们建立我们第一个号主平头哥PingtougeAuthor,PingtougeAuthor类的设计如下: /** * @author 平头哥 * @title: PingtougeAuthor * @projectName observer * @description: 号主平头哥 * @date 2019/9/1817:50 */public class PingtougeAuthor implements Author{ // 订阅者列表 private Vector<Reader> readers ; // 作者名称 private String name; // 文章 private String article; public PingtougeAuthor(String name){ this.name = name; this.readers = new Vector<>(); } /** * 添加关注者 * @param reader */ @Override public void addReader(Reader reader) { if (readers.contains(reader)) return; readers.add(reader); } /** * 移除关注者 * @param reader */ @Override public void deleteReader(Reader reader) { readers.remove(reader); } /** * 通知关注者 */ @Override public void notifyReader() { for (Reader reader:readers){ reader.reader(name,article); } } /** * 写文章 * @param article */ @Override public void writeArticle(String article){ this.article = article; notifyReader(); }}我们在建立王山、张三、李四三个订阅者,他们的具体设计如下: ...

September 20, 2019 · 2 min · jiezi

发布订阅模式和观察者模式真的不一样

背景设计模式的定义: 在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。设计模式并不能直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,它不是一个死的机制,它是一种思想,一种代码的形式。 每种语言对于各种设计模式都要它们自己的实现方式,对于某些设计模式来说,可能在某些语言下并不适用,比如工厂模式就不适用于JavaSctipt。模式应该用在正确的地方,而所谓正确的地方只有我们深刻理解模式的意图后,再结合项目的实际场景才知道。 观察者模式 (Observer Pattern)观察者模式定义了对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知,并自动更新。观察者模式属于行为模式,行为模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。 观察者模式还有一个别名叫“发布-订阅模式”,又或者“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐各通知订阅者。我们用报纸期刊的订阅来举例说明,当你订阅一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是定义里描述的“一对多”的依赖关系。 发布-订阅模式 (Pub-Sub Pattern)其实24种基本设计模式中,并没有发布-订阅模式,上面也解释了,它只是观察者模式的一个别称。但经过时间的沉淀,它已经强大起来,已经独立于观察者模式,成为一种新的设计模式。 在现在的发布-订阅模式中,发布者的消息不会直接发送给订阅者,这意味着发布者和订阅者都不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息,并相应的分发给它们的订阅者。举个例子,你在微博上关注了A,同时其他很多人也关注了A,当A发布动态时,微博就会为你们推送这条动态。A就是发布者,你就是订阅者,微博是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的。 观察者模式和发布-订阅模式有什么区别?我们先来看下者两个模式的实现结构: 观察者模式: 观察者(Observer)直接订阅(Subscribe)主体(Subject),而当主体被激活时,会触发(Fire Event)观察者里的事件。 发布-订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。 案例观察者模式// 有一家猎人公会,其中每个猎人都具体发布任务(publish),订阅任务(subscribe)的功能// 它们都有一个订阅列表记录谁订阅了自己// 定义一个猎人,包括姓名、级别、订阅列表function Hunter(name, level) { this,name = name this.level = level this.list = []}Hunter.prototype.publish = function (money) { console.log(this,level + '猎人: ' + this.name + '寻求帮助') this.list.forEach(function (callback) { callback && callback(money) })}Hunter.prototype.subscribe = function (target, callback) { console.log(this.level + '猎人: ' + this.name + '订阅了: ' + target.name) target.list.push(callback)}// 猎人公会走注册了几个猎人var hunterZhang = new Hunter('张三', '钻石')var hunterLi = new Hunter('李四', '黄金')var hunterWang = new Hunter('王五', '白银')var hunterZhao = new Hunter('赵六', '青铜')// 赵六等级较低,可能需要帮助,所以张三、李四、王五都订阅了赵六hunterZhang.subscribe(hunterZhao, function (money) { console.log('小明表示: ' + (money > 200 ? '' : '暂时很忙,不能') + '给予帮助')})hunterLi.subscribe(hunterZhao, function () { console.log('李四表示: 给予帮助')})hunterWang.subscribe(hunterZhao, function () { console.log('王五表示: 给予帮助')})// 赵六遇到困难,悬赏198寻求帮助hunterZhao.publish(198)// 猎人们(观察者)关联他们感兴趣的猎人(目标对象),如赵六,当赵六有困难时,会自动通知给他们(观察者)发布-订阅模式// 定义了一家猎人公会// 主要功能包含任务发布大厅(topics)、订阅任务(subscribe)、发布任务(publish)var HunterUnion = { type: 'hunt', topics: Object.create(null), subscribe: function (topic, callback) { if (!this.topics[topic]) { this.topics[topic] = [] } this.topics[topic].push(callback) }, publish: function (topic, money) { if (!this.topics[topic]) { return } for(var cb of this.topics[topic]) { cb(money) } }}// 定义一个猎人类,包括姓名和级别function Hunter(name, level) { this.name = name this.level = level}// 猎人可以在猎人公会发布、订阅任务Hunter.prototype.subscribe = function (task, fn) { console.log(this.level + '猎人: ' + this.name + '订阅了狩猎: ' + task + '的任务') HunterUnion.subscribe(task, fn)}Hunter.prototype.publish = function (task, money) { console.log(this.level + '猎人: ' + this.name + '发布了狩猎: ' + task + '的任务') HunterUnion.publish(task, money)}//猎人工会注册了几个猎人let hunterZhang = new Hunter('张三', '钻石')let hunterLi = new Hunter('李四', '黄金')let hunterWang = new Hunter('王五', '白银')let hunterZhao = new Hunter('赵六', '青铜')//张三,李四,王五分别订阅了狩猎tiger的任务hunterZhang.subscribe('tiger', function(money){ console.log('张三表示:' + (money > 200 ? '' : '不') + '接取任务')})hunterLi.subscribe('tiger', function(money){ console.log('李四表示:接取任务')})hunterWang.subscribe('tiger', function(money){ console.log('王五表示:接取任务')})//赵六订阅了狩猎sheep的任务hunterZhao.subscribe('sheep', function(money){ console.log('赵六表示:接取任务')})//赵六发布了狩猎tiger的任务hunterZhao.publish('tiger', 198)//猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎人工会(调度中心)关联起来的,他们没有直接的交流。观察者模式和发布-订阅模式最大的区别: 发布-订阅模式有事件调度中心。 ...

August 28, 2019 · 2 min · jiezi

设计方案浅析观察者模式和发布订阅模式的区别

有时候面试的时候可能会被问到: 观察者模式和发布订阅模式的区别? 没有区别吧? 好像有区别吧? 我们首先来看一下“观察者模式”和“发布订阅模式” 一、观察者设计模式理解设计模式在设计之初的是为了解决什么问题就能很好的在写代码中运用不同的设计模式。 所谓的观察者模式,其实为了实现松耦合(loosely coupled),也就是为了解耦。 当数据有变化有更新的时候,某个方法被调用,这时候就可以更新其他地方需要用到这个数据的地方。 举个例子: 以气象站为例,每当气象站测试数据有更新的时候,changed()方法都会被调用,于是我们在changed()方法中,更新气象仪器上的数据,比如温度、气压等等。 这样写没有毛病,但是,如果我们以后再changed()方法调用的时候,更新更多的信息,比如说湿度,这个时候我们需要去修改changed()方法的代码,这个就是紧耦合的坏处。 那怎么解决这个紧耦合的问题呢? 观察者模式中,changed()方法所在的实例对象,就是被观察者(subject),只需要维护一套观察者(observer)的集合,这些observer实现相同的接口,subject只需要知道:通知观察者(observer)时,需要调用哪一个统一方法就行。 我们再来看一下简单的例子 function Subject(){ this.observerData = [];}Subject.prototype = { add: function(observer) { this.observerData.push(observer); }, remove:function(observer) { var observerData = this.observerData; for(let i=0,length=observerData.length;i<length;i++){ if(observerData[i] === observer){ observerData.splice(i,1) } } }, notify: function() { var observerData = this.observerData; for(let i=0,length=observerData.length;i<length;i++){ observerData[i].update(); } }}function Observer(name){ this.name = name;}Observer.prototype = { update: function() { console.log('hello,' + this.name); }}var sub = new Subject();var obj1 = new Observer('saucxs');var obj2 = new Observer('songEagle');sub.add(obj1);sub.add(obj2);sub.notify();这时候输出的是 ...

May 23, 2019 · 1 min · jiezi

观察者模式与发布订阅模式

观察者模式与发布/订阅模式观察者模式概念一个被观察者的对象,通过注册的方式维护一组观察者对象。当被观察者发生变化,就会产生一个通知,通过广播的方式发送出去,最后调用每个观察者的更新方法。当观察者不再需要接受被观察者的通知时,被观察者可以将该观察者从所维护的组中删除。 实现这个实现包含以下组件: 被观察者:维护一组观察者, 提供用于增加和移除观察者的方法观察者:提供一个更新接口,用于当被观察者状态变化时,得到通知具体的被观察者:状态变化时广播通知给观察者,保持具体的观察者的信息具体的观察者:保持一个指向具体被观察者的引用,实现一个更新接口,用于观察,以便保证自身状态总是和被观察者状态一致的首先,对被观察者维护的一组观察者(列表)进行建模 function ObserverList() { this.observerList = []}ObserverList.prototype.add = function(obj) { return this.observerList.push(obj)}ObserverList.prototype.Empty = function() { this.observerList = []}ObserverList.prototype.removeAt = function(index) { this.observerList.splice(index, 1)}ObserverList.prototype.count = function() { return this.observerList.length}ObserverList.prototype.get = function(index) { if (index > -1 && index < this.observerList.length) { return this.observerList[index] }}// Extend an object with an extensionfunction extend(extension, obj) { for (var key in extension) { obj[key] = extension[key] }}接着,对被观察者以及其增加、删除、通知能力进行建模 function Subject() { this.observers = new ObserverList()}Subject.prototype.addObserver = function(observer) { this.observers.add(observer)}Subject.prototype.removeObserver = function(observer) { this.observers.removeAt(this.observers.IndexOf(observer, 0))}Subject.prototype.notify = function(context) { var observerCount = this.observers.count() for (var i = 0; i < observerCount; i++) { this.observers.get(i).update(context) }}接着,对观察者进行建模,这里的 update 函数之后会被具体的行为覆盖 ...

May 15, 2019 · 3 min · jiezi

RxJS原来应该这样用

引言最近帮潘佳琦解决了一个诡异的问题,然后突然发现⾃己对观察者感到迷茫了。 需求是⼀个注销按钮,如果是技术机构登陆,就调用技术机构的注销⽅法,如果是器具用户登陆,就调⽤器具⽤户的注销方法。当然,最优的解决⽅案并不是我下⽂所列的,既然功能不同,那就应该是两个对象。看来我们的⾯向对象运用得还不不够灵活。 原问题解决问题描述注销代码如下,只表达思想,别去深究具体的语法: logout(): void { this.departmentService.isLogin$.subscribe((isDepartmentLogin) => { if (isDepartmentLogin) { this.departmentLogout(); } }); this.systemService.isLogin$.subscribe((isTechnicalLogin) => { if (isTechnicalLogin) { this.technicalLogout(); } });}看着好像没啥错误啊?订阅获取当前登录用户状态,如果departmentService.isLogin$为true,表示是器具用户登录,则调用器具用户的注销方法;如果是systemService.isLogin$为true,表示是技术机构登录,则调用技术机构的注销方法。 然而,诡异的事情发生了: 首次打开系统,登录,登录成功。点击注销,也能注销成功。但是再登录系统,就登不进去了。也就是说,这个注销方法影响了后续的登录,当时就很懵圈,为什么呢? 后来多打了几条日志,才发现了问题所在: 原因分析根据Spring官方的Spring Security and Angular所述,官方做法是用一个boolean值来判断当前是否登录,从而进行视图的展示。 潘老师在新系统中也是根据官方的推荐进行实现的: let isLogin$ = new BehaviorSubject<boolean>();login() { ...... this.isLogin$.next(true);}logout() { ...... this.isLogin$.next(false);}一个可观察的boolean对象来判断当前用户是否登录,然后main组件订阅这个值,根据是否登录并结合ngIf来判断当前应该显示登录组件还是应用组件。 看这个图大家应该就明白了,问题出在了对subscribe的理解上。 点击注销,发起订阅,当isLogin$为true的时候就注销,注销成功。 下次登录时,这个订阅还在呢!然后点击登录,执行登录逻辑,将isLogin$设置为true,观察者就通知了订阅者,又执行了一遍注销逻辑。肯定登不上去了。 执行订阅之后,应该获取返回值,再执行取消订阅。 迷茫所以,这个问题出在本该订阅一次,但是subscribe是只要订阅过了,在取消订阅之前,我一直是这个可观察对象的观察者。 想到这我就迷茫了,就是我一订阅,一直到我取消订阅之前,这个可观察对象都要维护着观察者的列表。 那我们的网络请求用的不也是Observable吗?只是此Observable是由HttpClient构建好返回给我们的。那我们订阅了也没取消,那它是不是一直维护着这个关系,会不会有性能问题?难道我们之前的用法都错了吗? 这个问题一直困扰了我好多天,知道今天才在Angular官网上看到相关的介绍,才解决了我的迷茫。 HttpClient.get()方法正常情况下只会返回一个可观察对象,它或者发出数据,或者发出错误。有些人说它是“一次性完成”的可观察对象。 The HttpClient.get() method normally returns an observable that either emits the data or an error. Some folks describe it as a "one and done" observable. ...

April 26, 2019 · 1 min · jiezi

发布订阅模式与观察者模式

背景设计模式并非是软件开发的专业术语,实际上,“模式”最早诞生于建筑学。设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。通俗一点说,设计模式是在某种场合下对某个问题的一种解决方案。如果再通俗一点说,设计模式就是给面向对象软件开发中的一些好的设计取个名字。这些“好的设计”并不是谁发明的,而是早已存在于软件开发中。一个稍有经验的程序员也许在不知不觉中数次使用过这些设计模式。GoF(Gang of Four–四人组,《设计模式》几位作者)最大的功绩是把这些“好的设计”从浩瀚的面向对象世界中挑选出来,并且给予它们一个好听又好记的名字。设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,他不是一个死的机制,他是一种思想,一种写代码的形式。每种语言对于各种设计模式都有他们自己的实现方式,对于某些设计模式来说,可能在某些语言下并不适用,比如工厂方法模式对于javascript。模式应该用在正确的地方。而哪些才算正确的地方,只有在我们深刻理解了模式的意图之后,再结合项目的实际场景才会知道。。模式的社区一直在发展。GoF在1995年提出了23种设计模式,但模式不仅仅局限于这23种,后面增加到了24种。在这20多年的时间里,也许有更多的模式已经被人发现并总结了出来,比如一些JavaScript 图书中会提到模块模式、沙箱模式等。这些“模式”能否被世人公认并流传下来,还有待时间验证。观察者模式(Observer Pattern)观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。发布订阅模式(Pub-Sub Pattern)其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。举一个例子,你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的(你的关注,A的发布动态)。观察者模式和发布订阅模式有什么区别?我们先来看下这两个模式的实现结构:观察者模式:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。 发布订阅模式:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。我们再来看下这两个模式的代码案例:(猎人发布与订阅任务)观察者模式: //有一家猎人工会,其中每个猎人都具有发布任务(publish),订阅任务(subscribe)的功能 //他们都有一个订阅列表来记录谁订阅了自己 //定义一个猎人类 //包括姓名,级别,订阅列表 function Hunter(name, level){ this.name = name this.level = level this.list = [] } Hunter.prototype.publish = function (money){ console.log(this.level + ‘猎人’ + this.name + ‘寻求帮助’) this.list.forEach(function(item, index){ item(money) }) } Hunter.prototype.subscribe = function (targrt, fn){ console.log(this.level + ‘猎人’ + this.name + ‘订阅了’ + targrt.name) targrt.list.push(fn) } //猎人工会走来了几个猎人 let hunterMing = new Hunter(‘小明’, ‘黄金’) let hunterJin = new Hunter(‘小金’, ‘白银’) let hunterZhang = new Hunter(‘小张’, ‘黄金’) let hunterPeter = new Hunter(‘Peter’, ‘青铜’) //Peter等级较低,可能需要帮助,所以小明,小金,小张都订阅了Peter hunterMing.subscribe(hunterPeter, function(money){ console.log(‘小明表示:’ + (money > 200 ? ’’ : ‘暂时很忙,不能’) + ‘给予帮助’) }) hunterJin.subscribe(hunterPeter, function(){ console.log(‘小金表示:给予帮助’) }) hunterZhang.subscribe(hunterPeter, function(){ console.log(‘小金表示:给予帮助’) }) //Peter遇到困难,赏金198寻求帮助 hunterPeter.publish(198) //猎人们(观察者)关联他们感兴趣的猎人(目标对象),如Peter,当Peter有困难时,会自动通知给他们(观察者)发布订阅模式: //定义一家猎人工会 //主要功能包括任务发布大厅(topics),以及订阅任务(subscribe),发布任务(publish) let HunterUnion = { type: ‘hunt’, topics: Object.create(null), subscribe: function (topic, fn){ if(!this.topics[topic]){ this.topics[topic] = []; } this.topics[topic].push(fn); }, publish: function (topic, money){ if(!this.topics[topic]) return; for(let fn of this.topics[topic]){ fn(money) } } } //定义一个猎人类 //包括姓名,级别 function Hunter(name, level){ this.name = name this.level = level } //猎人可在猎人工会发布订阅任务 Hunter.prototype.subscribe = function (topic, fn){ console.log(this.level + ‘猎人’ + this.name + ‘订阅了狩猎’ + topic + ‘的任务’) HunterUnion.subscribe(topic, fn) } Hunter.prototype.publish = function (topic, money){ console.log(this.level + ‘猎人’ + this.name + ‘发布了狩猎’ + topic + ‘的任务’) HunterUnion.publish(topic, money) } //猎人工会走来了几个猎人 let hunterMing = new Hunter(‘小明’, ‘黄金’) let hunterJin = new Hunter(‘小金’, ‘白银’) let hunterZhang = new Hunter(‘小张’, ‘黄金’) let hunterPeter = new Hunter(‘Peter’, ‘青铜’) //小明,小金,小张分别订阅了狩猎tiger的任务 hunterMing.subscribe(’tiger’, function(money){ console.log(‘小明表示:’ + (money > 200 ? ’’ : ‘不’) + ‘接取任务’) }) hunterJin.subscribe(’tiger’, function(money){ console.log(‘小金表示:接取任务’) }) hunterZhang.subscribe(’tiger’, function(money){ console.log(‘小张表示:接取任务’) }) //Peter订阅了狩猎sheep的任务 hunterPeter.subscribe(‘sheep’, function(money){ console.log(‘Peter表示:接取任务’) }) //Peter发布了狩猎tiger的任务 hunterPeter.publish(’tiger’, 198) //猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎人工会(调度中心)关联起来的,他们没有直接的交流。观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,这种处理方式比较直接粗暴,但是会造成代码的冗余。而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰,消除了发布者和订阅者之间的依赖。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。观察者模式是不是发布订阅模式网上关于这个问题的回答,出现了两极分化,有认为发布订阅模式就是观察者模式的,也有认为观察者模式和发布订阅模式是真不一样的。其实我不知道发布订阅模式是不是观察者模式,就像我不知道辨别模式的关键是设计意图还是设计结构(理念),虽然《JavaScript设计模式与开发实践》一书中说了分辨模式的关键是意图而不是结构。如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。不过,不管他们是不是同一个设计模式,他们的实现方式确实有差别,我们在使用的时候应该根据场景来判断选择哪个。 ...

March 29, 2019 · 2 min · jiezi

行为型模式:观察者模式

行为型模式:观察者模式十一大行为型模式之七:观察者模式。简介姓名 :观察者模式英文名 :Observer Pattern价值观 :盯着你怎么着个人介绍 :Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。(来自《设计模式之禅》)你要的故事想来想去,就拿我们现在生活中最常体会到的事情来讲观察者模式–朋友圈。小明、小红、小东 3 人是好朋友,最近他们的父母都给安排了手机,刚用上手机那是相当的兴奋呀。他们立马从 QQ 转投到微信的怀抱,对微信的朋友圈玩的不亦乐乎,什么事情都往上面发。突然有一天,小明和小红因为一些小事争执闹别扭了,原因就是他们对一道数学题有不同的见解。就跟我们小时候和朋友玩得好好的,突然因为一点小事就闹翻了。小红比较孩子气,立马就屏蔽了小明的朋友圈,不想再看到有关小明相关的信息。故事就是这么一回事,关注点就在这朋友圈上。朋友圈就是运用观察者模式的一个很好的样例。为什么这么说?我们发朋友圈的时候,那些没有屏蔽我们朋友圈的好友,会收到信息推送。也就是没有屏蔽我们朋友圈的好友其实是订阅了我们朋友圈,好友相当于观察者,我们是被观察的对象。符合观察者模式这个关系。我们通过代码来描述小明、小红、小东他们在朋友圈玩的场景。利用观察者模式,需要观察对象和被观察对象,所以我们先定义 2 个接口,分别是 Observable (可被观察接口) 和 Observer (观察者接口)。实现 Observable 接口的对象说明是可被订阅观察的,所以它需要 addObserver() 新增订阅者方法和 removeObserver() 移除订阅者方法,另外还有一个是必须的,就是通知各个订阅者消息的方法 notifyObservers()。那 Observable 接口代码如下所示。interface Observable { void addObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(String message);}实现 Observer 接口的对象说明是可以去订阅观察的,也就是说可以接收被订阅的对象发出来的消息,那就需要一个接收消息的方法 update()。代码如下所示。interface Observer { void update(String name, String message);}为了让大家不混淆,先把观察者和被观察者分离开,其实在这个例子中,观察者和被观察者是同一个对象 User 的。这里就分开,分成 User 和 Friend,后面会给出正确的代码,稍安勿躁哈。这里 User 作为被观察者,实现了 Observable 接口,而 Friend 作为观察者,实现了 Observer 接口。代码如下。class User implements Observable { private List<Observer> friends; private String name; public User(String name) { this.name = name; this.friends = new LinkedList<>(); } public void sendMessage(String message) { this.notifyObservers(message); } @Override public void addObserver(Observer observer) { this.friends.add(observer); } @Override public void removeObserver(Observer observer) { this.friends.remove(observer); } @Override public void notifyObservers(String message) { this.friends.forEach(friend -> { friend.update(this.name, message); }); }}class Friend implements Observer { private String name; public Friend(String name) { this.name = name; } @Override public void update(String name, String message) { System.out.println("【" + this.name + “】看到【” + name + “】发的朋友圈:” + message); }}public class ObserverTest { public static void main(String[] args) { User xiaoMing = new User(“小明”); Friend xiaoHong = new Friend(“小红”); Friend xiaoDong = new Friend(“小东”); xiaoMing.addObserver(xiaoHong); xiaoMing.addObserver(xiaoDong); xiaoMing.sendMessage(“今天真开心”); // 小红和小明闹别扭了,小红取消订阅小明的朋友圈 xiaoMing.removeObserver(xiaoHong); xiaoMing.sendMessage(“希望明天也像今天一样开心”); }}打印结果:【小红】看到【小明】发的朋友圈:今天真开心【小东】看到【小明】发的朋友圈:今天真开心【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心看到代码执行结果,小红和小东都订阅了小明的朋友圈,小明发了朋友圈:今天真开心。他们俩都收到了,因为小红和小明闹别扭,小红取消订阅小明的朋友圈,所以小明后来发的朋友圈,小红没收到。上面代码其实是不对的,不应该用 User 和 Friend 2 个类来定义。如果小明订阅小红和小东的朋友圈呢?这样实现比较麻烦,主要是为了分清 观察者 和 被观察者 这 2 个概念,通过上面的例子应该分清楚了 2 个概念了,那就可以来看正确的代码,小明、小红、小东他们其实都是观察者和被观察者,所以我们用 User2 来定义他们就可以,User2 实现了 Observable 和 Observer 接口。代码如下。class User2 implements Observable, Observer { private List<Observer> friends; private String name; public User2(String name) { this.name = name; this.friends = new LinkedList<>(); } @Override public void addObserver(Observer observer) { this.friends.add(observer); } @Override public void removeObserver(Observer observer) { this.friends.remove(observer); } @Override public void notifyObservers(String message) { this.friends.forEach(friend -> { friend.update(this.name, message); }); } @Override public void update(String name, String message) { System.out.println("【" + this.name + “】看到【” + name + “】发的朋友圈:” + message); } public void sendMessage(String message) { this.notifyObservers(message); }}public class ObserverTest { public static void main(String[] args) { User2 xiaoMing2 = new User2(“小明”); User2 xiaoHong2 = new User2(“小红”); User2 xiaoDong2 = new User2(“小东”); xiaoMing2.addObserver(xiaoHong2); xiaoMing2.addObserver(xiaoDong2); xiaoMing2.sendMessage(“今天真开心”); xiaoMing2.removeObserver(xiaoHong); xiaoMing2.sendMessage(“希望明天也像今天一样开心”); xiaoHong2.addObserver(xiaoMing2); xiaoHong2.addObserver(xiaoDong2); xiaoHong2.sendMessage(“今天和小明吵架了,屏蔽他的朋友圈”); xiaoDong2.addObserver(xiaoMing2); xiaoDong2.addObserver(xiaoHong2); xiaoDong2.sendMessage(“小明和小红吵架了,夹在中间好尴尬”); }}打印结果:【小红】看到【小明】发的朋友圈:今天真开心【小东】看到【小明】发的朋友圈:今天真开心【小红】看到【小明】发的朋友圈:希望明天也像今天一样开心【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心【小明】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈【小东】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈【小明】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬【小红】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬从代码中,我们看到小明、小红、小东 3 个人互相订阅朋友圈,当然中途小红屏蔽了小明的朋友圈。这就是 观察者 和 被观察者 刚好是同一个对象的实现。总结观察者模式 是一个比较特殊的设计模式,它定义了触发机制,观察者只要订阅了被观察者,就可以第一时间得到被观察者传递的信息。在工作中,使用观察者模式的场景也比较多,比如消息队列消费,Android 开发中的事件触发机制等等。好,观察者模式就到这。推荐阅读:行为型模式:迭代器模式行为型模式:策略模式行为型模式:责任链模式设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。 ...

March 11, 2019 · 2 min · jiezi