观察者模式 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接口即可,满足开闭原则。
 
一个观察者,也可能是一个被观察者,如果出现观察者调用链,将会发生多米诺骨牌效应不断地进行调用,后果可能是难以预知的。
所以要谨慎识别双重身份的对象,也就是即是观察者也是被观察者的对象。
 
被观察者不知道具体的观察者,也更不可能知道观察者具体的行为
当发生状态变化时,被观察者一个小举动,可能引起很大的性能消耗,而被观察者对此毫不知情,可能仍旧以为云淡风轻。
 
当一个对象状态的改变,需要同时改变其他对象时,可以考虑观察者模式
当一个对象必须通知其他人时,但是他又不知道到底是谁时,可以考虑观察者模式
或者将一个抽象模型中的两个关联部分解耦,以便独立发展,提高复用性,解耦不表示断开,仍旧需要联系,可以借助于观察者模式进行联系
总之
观察模式通过引入观察者角色,将调用者与被调用者解耦,通过观察者角色联系。
但凡类似“广播”“发布订阅”的场景,都可以考虑是否可用。
 

最新文章

  1. MediaElement.js对不同浏览器的支持
  2. CentOS终端界面登入Linux
  3. pythonweb自动化测试
  4. Contest2037 - CSU Monthly 2013 Oct (problem D :CX and girls)
  5. 更新 requests 包之后报 has no attribute &#39;__getitem__&#39; 的错
  6. gvim &amp; vim
  7. doxygen学习笔记
  8. nyist 202 红黑树(二叉树中序遍历)
  9. spring 整合redis
  10. line-height系列——定义和工作原理总结
  11. #define宏与const的区别
  12. jvm007 jvm知识点总览
  13. 途虎养车Tuhu商城系统开发
  14. 如何删除chrome地址栏里面曾经输错的地址
  15. 高通HAL层之Sensor HAL
  16. PS 图像调整算法——亮度调整
  17. mysql 分库分表转
  18. 【读书笔记】《深入浅出Webpack》
  19. Windows 7系统下安装和卸载删除IE的方法
  20. 重新装kafka

热门文章

  1. 距离度量以及python实现(二)
  2. github下载和上传项目
  3. Bumblebee之负载、限流和故障处理实践
  4. Java8新特性之三:Stream API
  5. 微信小程序之表单验证
  6. 强化学习(十九) AlphaGo Zero强化学习原理
  7. 学习web的第二天
  8. Dynamics 365 CE中使用FetchXML进行聚合运算
  9. 2018-12-25 VS Code英汉词典v0.0.8: 批量翻译文件部分命名
  10. SqlServer_查看SQLServer版本信息