观察者模式 Observer

意图

定义对象一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖他的对象都得到通知并自动更新。
别名:依赖(Dependents),发布订阅(Publish-Subscribe)源-监听(Source-Listener)
 
《Hold On, We're Going Home》是加拿大说唱歌手德雷克与制作组合Majid Jordan合作的节奏布鲁斯歌曲
第一句“I got my eyes on you”就是“我一直关注你”
 
I got my eyes on you,
You're everything that I see
I want your hot love and emotion, endlessly
I can't get over you,
You left your mark on me......
 
电视剧中,也是经常出现”观察、监视“,比如《黎明之前》
是刘江执导2010年出品的谍战剧,由吴秀波、林永健、陆剑民、海清等领衔主演。豆瓣评分高达9.2。
有一段周汉亭被盯梢的桥段,有负责门口监视的,有负责电话汇报情况的,有负责指挥的....他的一举一动都在敌人的监视之内,引发无数个探子连锁行动。
 
歌词中因为喜欢妹子所以持续关注,妹子是目标,歌手是观察者。
电视剧中敌人为了破坏我党工作,所以监视,周汉亭是目标,众多探子是观察者。
通过对目标的观察,观察者可以获得事物的动向情况,进而做出进一步的行动。这就是观察。
 
在程序中,也经常会出现这种场景
一个系统中必然由多个相互协作的类组成,很常见的问题就是维持状态的一致性或者说联动效果。
观察者模式就是为了解决这种问题,处理“协作者”之间的一致性与联动问题。
 
比如,数据库中对某个字段进行了更新,可能需要同步修改其他字段
 
比如,执行一个main方法,IDE的窗口的联动效果,如下图所示,点击run执行后
底部状态栏会显示Build状态,很快完成后就开始运行,侧边栏显示运行状态,然后控制台打印输出信息
这是一系列的联动效果
比如,同一份数据有多种图表展示,如果数据发生变化,每个图表都需要发生变化

结构

假设目标为Subject ,观察者为Observer(一个或者多个)
最简单的实现方式,就是Subject直接调用Observer的方法。
当事件发生后,直接调用Observer的相关方法。
伪代码如下
Subject内使用List维护观察者
当事件发生,也就是方法f()中,循环通知观察者
省略了观察者的维护工作,也就是添加和删除
class Subject{
List<Observer> observerList = new ArrayList<>();
void f(){
//do sth...
for(Observer o:observerList){
//调用相关方法
o.doSthElse();
}
}
依赖倒置原则中,要求应该面向抽象进行编程,而不是面向细节。
上面的结构中,不管是目标还是观察者的扩展都不方便,所以抽象提取。
 
这就是观察者模式的基本结构。
抽象观察者角色Observer
为所有的具体的观察者定义一个接口,得到主题的通知信息后,进行同步响应。
一般包含一个方法叫做update()用以同步响应
抽象主题角色Subject
主题角色把所有观察者对象保存在集合中,提供管理工作,添加和删除
并且,提供通知工作,也就是调用相关观察者的update
具体主题角色ConcreteSubject
实现抽象主题接口协议,当状态方式发生变化时,对观察者进行通知
具体观察者角色ConcreteObserver
实现抽象观察者定义的接口,完成自身相关的同步更新活动

代码示例

抽象观察者角色,提供统一的更新方法
package observer;
public interface Observer {
void update();
}
抽象的Subject,内部使用List<Observer>保存观察者对象
提供了attach和dettach方法用于添加和删除观察者
并且提供了通知方法,就是遍历调用Observer
package observer;
import java.util.LinkedList;
import java.util.List; public abstract class Subject { List<Observer> observerList; Subject() {
observerList = new LinkedList<>();
} void attach(Observer o) {
observerList.add(o);
} void dettach(Observer o) {
observerList.remove(o);
} void notifyObservers() {
for (Observer o : observerList) {
o.update();
}
}
}
具体的主题角色,他有一个行动方法,行动后通知其他的观察者
观察者在父类中列表里面定义
package observer;
public class ConcreteSubject extends Subject {
public void action() {
System.out.println("下雨了");
super.notifyObservers();
}
}
具体的观察者,实现具体的行动
package observer;
public class ConcreateObserver implements Observer {
@Override
public void update() {
System.out.println("那我收衣服了");
}
}
测试代码
package observer;
public class Test {
public static void main(String[] args) {
Observer o1 = new ConcreateObserver();
ConcreteSubject subject = new ConcreteSubject();
subject.attach(o1);
subject.action();
}
}

 

如果新增加一个观察者
package observer;
public class ConcreteObserver1 implements Observer {
@Override
public void update() {
System.out.println("那我得把窗户关上");
}
}
测试代码
如上图所示,有两个观察者(比如张三和李四),当包租婆大喊一声下雨了之后,他们都得到通知
张三去收衣服了,李四把自己的窗户关上了
 
以上就是观察者模式的简单示例。
 
观察者模式的核心在于对于观察者的管理和维护,以及发送通知。
消息的发布订阅,在程序中就是消息发布者调用订阅者的相关方法
观察者模式将发布者与订阅者进行解耦,不再是直接的方法调用,通过引入Observer角色,完成了发布者与具体订阅者之间的解耦
也是一种形式的“方法调用”的解耦

Java的观察者模式

 
观察者接口Observer
是一个interface,定义了一个update方法
void update(Observable o, Object arg);
接受两个参数,一个是被观察对象,一个是参数
被观察角色Observerable 等同于前文Subject
内部使用Vector维护观察者Observer
提供了对观察者的管理相关方法,添加、删除、计算个数、删除、删除所有等
 
Observerable内部使用标志位记录是否已经发生变化
    private boolean changed = false;
 
发生变动后,设置为true,通知后设置为false
 
addObserver(Observer o) 添加指定观察者
deleteObserver(Observer o)  删除指定观察者
deleteObservers()      删除所有观察者
countObservers 返回观察者个数
notifyObservers()
notifyObservers(Object arg) 
通知所有观察者
一个无参,一个有参
参数传递给Observer的update
 
Observerable的实现原理很简单:
  • 使用Vector保存观察者,提供了添加、删除、删除全部的方法,并且可以返回观察者的个数。
  • 如果的确发生变动,将会通知所有的观察者
提供了两个版本的notifyObservers,一个有参,有个无参,参数对应update方法的第二个参数
 
通知后,将会清除变动状态
 
Observable中Vector<Observer>是私有的,也并没有提供访问器,只是可以添加、删除、或者清除所有
所以子类并不知道具体的Observer
代码示例
package observer.java;
import java.util.Observable;
public class Subject extends Observable {
public void changeState() {
System.out.println("下雨了........");
setChanged();
notifyObservers();
}
}
package observer.java;
import java.util.Observable;
import java.util.Observer;
public class Watcher1 implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("被观察者:" + o + " ,参数 " + arg + " ,观察者1");
}
}

 

package observer.java;
import java.util.Observable;
import java.util.Observer;
public class Watcher2 implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("被观察者:" + o + " ,参数 " + arg + " ,观察者2");
}
}
 
Subject 继承Observable类,自定义了changeState()方法
在方法中,调用
    setChanged();
    notifyObservers();
完成状态改变和通知。
 
从打印信息可以看得出来
update方法接收到的第一个参数,就是我们的被观察者对象
第二个参数可以用来封装传递信息
 
所以在java中,除非场景特殊,你又不需要自己写观察者模式了,已经内置了
通过继承和实现相应的类和接口即可。
 
GOF的设计模式出版于95,JDK 1.0始于1996,所以,Java天然支持某些设计模式也很正常
而且,设计模式是经验总结,GOF将他们归纳总结使之广为人知,但是并不代表这些经验史无前例
JDK的开发者人家本身就有这些“经验”也不足为奇。

与中介者模式区别

观察者模式用于一对多依赖场景中的解耦,通过引入Observer角色,将消息发布者与具体的订阅者进行解耦
中介者模式是将系统内部多对多的复杂耦合关系,借助于中介者进行解耦,将网状结构,简化为星型结构,将多对多转换为一对多 
 
他们都能够实现消息发布者与接收者的解耦,消息发布者都不知道具体的消息接收者
发起消息的Colleague同事类角色是被观察者,中介类Mediator是观察者,调用其他的同事类进行协作
 
尽管观察者模式强调“一致性通信”
中介者模式强调“内部组件协作”
但是根本还是方法执行时,需要同步调用其他对象
 
两个模式之间是一种类似的关系,在有些场景可替代转换。
如果协作关系比较简单,可以实现为一对多的形式,使用观察者模式
如果协作关系更加复杂,那么就可以使用中介者模式

总结

观察者模式是在一对多的依赖场景中,对消息发布者和消息订阅者的解耦
在观察者和被观察者之间建立了一个抽象的耦合,而不是强关联
通过引入观察者角色,发布者不依赖具体的观察者,从而Subject和Observer可以独立发展
被观察者仅仅知道有N个观察者,他们以集合的形式被管理,都是Observer角色,但是完全不知道具体的观察者
 
观察者模式的支持广播,被观察者会向所有的观察者发送消息。
 
增加新的观察者时,不需要修改客户端代码,只需要扩展Observer接口即可,满足开闭原则。
 
一个观察者,也可能是一个被观察者,如果出现观察者调用链,将会发生多米诺骨牌效应不断地进行调用,后果可能是难以预知的。
所以要谨慎识别双重身份的对象,也就是即是观察者也是被观察者的对象。
 
被观察者不知道具体的观察者,也更不可能知道观察者具体的行为
当发生状态变化时,被观察者一个小举动,可能引起很大的性能消耗,而被观察者对此毫不知情,可能仍旧以为云淡风轻。
 
当一个对象状态的改变,需要同时改变其他对象时,可以考虑观察者模式
当一个对象必须通知其他人时,但是他又不知道到底是谁时,可以考虑观察者模式
或者将一个抽象模型中的两个关联部分解耦,以便独立发展,提高复用性,解耦不表示断开,仍旧需要联系,可以借助于观察者模式进行联系
总之
观察模式通过引入观察者角色,将调用者与被调用者解耦,通过观察者角色联系。
但凡类似“广播”“发布订阅”的场景,都可以考虑是否可用。
 

观察者模式 Observer 发布订阅模式 源 监听 行为型 设计模式(二十三)的更多相关文章

  1. 观察者模式 vs 发布-订阅模式

    我曾经在面试中被问道,_“观察者模式和发布订阅模式的有什么区别?” _我迅速回忆起“Head First设计模式”那本书: 发布 + 订阅 = 观察者模式 “我知道了,我知道了,别想骗我” 我微笑着回 ...

  2. js之观察者模式和发布订阅模式区别

    观察者模式(Observer) 观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进 ...

  3. 观察者模式Vs发布订阅模式

    1)观察者模式 观察者模式通俗的讲就是我们平事件调用(click/change等等) 大家先看这个图片.我们被观察者Subject(监听某个事件)发生改变时,观察者Observer监听到没改变做出调整 ...

  4. java设计模式之-观察者模式(发布-订阅模式)

    1.观察者模式定义  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象. 这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己. 2.观察者模式结构 ...

  5. C#设计模式--观察者模式(发布-订阅模式)

    0.C#设计模式--简单工厂模式 1.C#设计模式--工厂方法模式 2.C#设计模式--抽象工厂模式 3.C#设计模式--单例模式 4.C#设计模式--建造者模式 5.C#设计模式--原型模式 6.C ...

  6. SpringBoot事件监听机制及观察者模式/发布订阅模式

    目录 本篇要点 什么是观察者模式? 发布订阅模式是什么? Spring事件监听机制概述 SpringBoot事件监听 定义注册事件 注解方式 @EventListener定义监听器 实现Applica ...

  7. 浅谈vue响应式原理及发布订阅模式和观察者模式

    一.Vue响应式原理 首先要了解几个概念: 数据响应式:数据模型仅仅是普通的Javascript对象,而我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率. 双向绑定:数据改变,视图 ...

  8. C# 委托和事件 与 观察者模式(发布-订阅模式)讲解 by天命

    使用面向对象的思想 用c#控制台代码模拟猫抓老鼠 我们先来分析一下猫抓老鼠的过程 1.猫叫了 2.所有老鼠听到叫声,知道是哪只猫来了 3.老鼠们逃跑,边逃边喊:"xx猫来了,快跑啊!我是老鼠 ...

  9. javascript中的发布订阅模式与观察者模式

    这里了解一下JavaScript中的发布订阅模式和观察者模式,观察者模式是24种基础设计模式之一. 设计模式的背景 设计模式并非是软件开发的专业术语,实际上设计模式最早诞生于建筑学. 设计模式的定义是 ...

随机推荐

  1. 干货,分享一次完整的CentOS升级内核脚本。

    一.安装常用包 yum install wget vim screen net-tools lrzsz -y wget -O /etc/yum.repos.d/epel.repo http://mir ...

  2. 怎么动态生成js变量

    动态生成全局变量: //简单的用字符串作为变量名 window['hello'] = "hello, world"; alert(hello);   //批量定义 for(var  ...

  3. Eclipse 出现项目没有错但是项目名称却有红色感叹号或者红叉的解决办法

    错误的起因是本人因为一不小心点了下面圈出来的某一个按钮,具体记不清楚了(好像是"remove from build path"),然后整个项目变得很奇怪了,所有的包都变成了一个普通 ...

  4. ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

    一.前言 在涉及到后端项目的开发中,如何实现对于用户权限的管控是需要我们首先考虑的,在实际开发过程中,我们可能会运用一些已经成熟的解决方案帮助我们实现这一功能,而在 Grapefruit.VuCore ...

  5. 【转】委托的N种写法,你喜欢哪种?

    一.委托调用方式 1. 最原始版本: delegate string PlusStringHandle(string x, string y); class Program { static void ...

  6. 解决SpannableString在Android组件间传递时显示失效的问题

    问题:在A activity中传递一个SpannableString到B activity中,并最终传递到B activity中的TextView中,但是没有展示出Span效果. 解决:阅读TextV ...

  7. 配置rsync+inotify实时同步

    与上一篇同步做 配置rsync+inotify实时同步 1:调整inotify内核参数 在linux内核中,默认的inotify机制提供三个调控参数:max_queue_events.max_user ...

  8. 【php性能优化】关于写入文件操作的取舍方案

    对于使用php对文件进行写入操作有两种方案一种使用 file_put_contents() 和 fopen()/fwrite()/fclose() 两种方案至于应该怎么选,我觉得应该分情况选择,下面是 ...

  9. BFPRT算法

    解决的问题:在一个数组中找到最小的k个数 常规解法:1.排序,输出前k个数,时间复杂度O(n*log(n)). 2.利用一个大小为k的大根堆,遍历数组维持大根堆,最后返回大根堆就可以了,时间复杂度O( ...

  10. 设计模式 | 装饰模式(decorator)

    定义: 装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 结构:(书中图,侵删) 一个被装饰接口类:从具体类中抽象出来, ...