模式的秘密——观察者模式

一、   观察者模式的定义

定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

二、   认识观察者模式

1、       目标与观察者之间的关系

目标与观察者之间是一对多的关系,当然观察者只有一个也是合理的。

2、       单向依赖

观察者和目标是单向依赖的关系,只有观察者依赖目标,而不是目标依赖观察者,只有目标知道什么时候通知观察者,整个过程中观察者都是被动的,被动的等待目标的通知。

3、       命名建议

第一,目标接口的定义,建议在名称后面跟上Subject;

第二,观察者接口的定义,建议在名称后面跟上Observer;

第三,观察者接口的更新方法,建议名称为update,参数可以根据需要自己定义。

4、       触发通知的时机

一般情况是在完成状态维护后触发通知,因为通知会传递数据,不能够先通知再改变数据。

5、       观察者模式的调用顺序示意图

6、       通知的顺序

多个观察者之间的通知顺序是不确定的,彼此之间是平行的,观察者之间不应该有相互依赖的关系。

三、   观察者模式的实例

我们以天气状况为例,不同的观察者会根据天气情况选择做不同的事情。

1、       首先,我们需要定义观察者接口,Observer.java

/*

* 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象

*/

public interface Observer{

/*

* 更新的接口

* @param subject 传入目标对象,方便获取相应的目标对象的状态

*/

public voidupdate(WeatherSubject subject);

}

2、       接下来,我们定义观察者的构造类,并实现观察者接口

/*

* 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致

*/

public class ConcreteObserverimplements Observer {

// 观察者的名字

private String observerName;

// 天气内容的情况,这个消息从目标处获取

private String weatherContent;

// 提醒的内容

private String remindThing;

public String getObserverName() {

return observerName;

}

public voidsetObserverName(String observerName) {

this.observerName = observerName;

}

public String getWeatherContent() {

return weatherContent;

}

public voidsetWeatherContent(String weatherContent) {

this.weatherContent = weatherContent;

}

public String getRemindThing() {

return remindThing;

}

public voidsetRemindThing(String remindThing) {

this.remindThing = remindThing;

}

/*

* 获取目标类的状态同步到观察者的状态中 (non-Javadoc)

*

* @see Observer#update(Subject)

*/

@Override

public voidupdate(WeatherSubject subject) {

weatherContent = ((ConcreteWeatherSubject) subject).getWeatherContent();

System.out.println(observerName + "收到了" + weatherContent + "," + remindThing);

}

}

3、       然后,我们需要定义目标对象,我们以天气作为目标对象

import java.util.ArrayList;

import java.util.List;

/*

* 目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口

*/

public class WeatherSubject {

// 用来保存注册的观察者对象

privateList<Observer> observers = new ArrayList<>();

// attachdetach notifyObservers

/*

* 把订阅天气的人添加到订阅者列表中

*/

publicvoid attach(Observer observer) {

observers.add(observer);

}

/*

* 删除集合中的指定订阅天气的人

*/

publicvoid detach(Observer observer) {

observers.remove(observer);

}

/*

* 通知所有注册的观察者对象

*/

protectedvoid  notifyObservers() {

for(Observerobserver: observers){

observer.update(this);

}

}

}

4、       接着就是目标对象的构造类,并继承自目标对象

/*

* 具体的目标对象,负责把有关状态存入到相应的观察者对象中

*/

public classConcreteWeatherSubject extendsWeatherSubject {

// 获取天气的内容信息

private String weatherContent;

public String getWeatherContent() {

return weatherContent;

}

public voidsetWeatherContent(String weatherContent) {

this.weatherContent = weatherContent;

this.notifyObservers();

}

}

5、       最后,我们来编写客户端测试一下效果

public class Client {

public static void main(String[] args) {

// 1、创建目标

ConcreteWeatherSubjectweather = new ConcreteWeatherSubject();

// 2、创建观察者

ConcreteObserverobserverOne = new ConcreteObserver();

observerOne.setObserverName("观察者1");

observerOne.setRemindThing("适合出行旅游");

ConcreteObserverobserverTwo = new ConcreteObserver();

observerTwo.setObserverName("观察者2");

observerTwo.setRemindThing("适合逛街购物");

// 3、注册观察者

weather.attach(observerOne);

weather.attach(observerTwo);

// 4、目标发布天气

weather.setWeatherContent("明天天气晴朗,气温25度");

}

}

6、       结果

四、   观察者模式的两种方式

观察者模式的实现有两种方式:推模型和拉模型。

推模型:目标对象主动向观察者推送目标的详细信息,推送的信息通常是目标对象的全部或部分数据。

拉模型:目标对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动向目标对象中获取,相当于是观察者从目标对象中拉取数据。一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者。

上面的实例,我们就使用的是拉模型,接下来我们对上面的实例进行小小的修改,实现推模型。

1、首先,我们需要将Observer.java中的update方法进行修改,如下所示:

public void update(String content);

我们将update的参数由WeatherSubject修改为String。

2、然后,我们将ConcreteObserver.java中继承的update方法进行修改,如下所示:

@Override

public void update(String content) {

weatherContent = content;

System.out.println(observerName + "收到了" + weatherContent + "," + remindThing);

}

3、接着,我们把WeatherSubject.java中的notifyObservers方法进行适当的修改,如下所示:

protected void  notifyObservers(String content) {

for(Observer observer: observers){

observer.update(content);

}

}

4、最后,我们还需要将ConcreteWeatherSubject.java中的 setWeatherContent方法进行修改,如下所示:

public voidsetWeatherContent(String weatherContent) {

this.weatherContent = weatherContent;

this.notifyObservers(weatherContent);

}

5、我们来运行一下,看看结果

推模型和拉模型的比较:

推模型是假定目标对象知道观察者需要的数据。推模型会使观察者对象难以复用。

拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值。拉模型下,update方法的参数是目标对象本身,基本上可以适应各种情况的需要。

五、   利用Java提供的观察者实现

在java.util包中提供了Observable类,还有一个接口Observer提供了update()方法

使用Java提供的方法有以下四点好处:

第一,不需要再定义观察者和目标的接口了,JDK帮忙定义了

第二,具体的目标实现里面不需要再维护观察者的注册信息了,这个在Java中的Observable类里面已经帮忙实现好了。

第三,触发通知的方式有一点变化,要先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能。

第四,具体观察者的实现里面,update方法其实能同时支持推模型和拉模型,这个是Java在定义的时候,就已经考虑进去了。

六、   使用Java提供的接口实现观察者模式

1、       定义目标对象类,并且继承Java中的Observable类

/*

* 目标天气的具体实现类

*/

public classConcreteWeatherSubject extendsObservable {

// 天气的内容

private String content;

public String getContent() {

return content;

}

public voidsetContent(String content) {

this.content = content;

//使用Java中的Observer模式的时候,在通知之前,需要先调用setChanged()方法

this.setChanged();

/*

* 在调用通知方法的时候,是选择推模型还是拉模型就需要根据具体情况确定了

*this.notifyObservers();拉模型

*this.notifyObservers(content); 推模型

*/

this.notifyObservers(content);

}

}

2、       定义观察者对象,并实现Observer接口

//具体的观察者对象

public class ConcreteObserverimplements Observer {

//观察者名称变量

private String observerName;

/*

* (non-Javadoc)

* @seejava.util.Observer#update(java.util.Observable, java.lang.Object)

* 第一个参数传递的目标应用,采用的是拉的方式

* 第二个参数推送的内容,采用的是推送的方式

*/

@Override

public voidupdate(Observable o, Object arg) {

// TODO Auto-generated method stub

//推方式

System.out.println(observerName+"收到了推送消息:"+arg);

//拉方式

System.out.println(observerName+"拉取了消息:"+((ConcreteWeatherSubject)o).getContent());

}

public String getObserverName() {

return observerName;

}

public voidsetObserverName(String observerName) {

this.observerName = observerName;

}

}

3、       编写测试方法

public class Client {

public static void main(String[] args) {

// TODO Auto-generated method stub

//创建天气目标

ConcreteWeatherSubjectweatherSubject = new ConcreteWeatherSubject();

//创建观察者

ConcreteObserverobserver1 = new ConcreteObserver();

observer1.setObserverName("观察者1");

ConcreteObserverobserver2 = new ConcreteObserver();

observer2.setObserverName("观察者2");

//注册观察者

weatherSubject.addObserver(observer1);

weatherSubject.addObserver(observer2);

//目标更新天气情况

weatherSubject.setContent("天气晴,气温30度");

}

}

4、       运行结果

七、   观察者模式的优缺点

1、       观察者的优点

第一,观察者模式实现了观察者和目标之间的抽象耦合;

原本目标对象在状态发生改变的时候需要直接调用所有的观察者对象,但是抽象出观察者接口之后,目标和观察者之间就只是抽象层面上的耦合,目标只是知道观察者的接口,并不知道观察者的类,从而实现了目标与具体的观察者之间的解耦。

第二,观察者模式实现了动态联动;

由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态的控制注册的观察者来控制某个动作的联动范围,从而实现动态联动。

第三,观察者模式支持广播通信。

目标发送通知给观察者是面向所有注册的观察者,所以目标每次通知的信息就要对所有注册的观察者进行广播,也可以在目标上添加新的方法来限制广播的范围。

2、       观察者的缺点

第一,可能会引起无谓的操作;

由于观察者模式每次都是广播通信,不管需不需要每个观察者都会被调用update方法。如果观察者不需要执行相应的方法,调用了方法导致误更新那就麻烦了。

八、   观察者模式的使用场景

第一,当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态改变;

第二,如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变;

第三,当一个对象必须通知其他的对象,但是你又希望这个对象和其他被通知的对象是松散耦合的。

九、   区别对待观察者场景

在使用观察者模式解决实际问题的时候,不同的观察者可能对于同一个目标关注的内容并不相同,比如:同样是天气信息,观察者1只关注下雨的天气情况,而观察者2只关注下雨和下雪的天气情况,那么怎么处理这样的场景呢,我们只有区别对待观察者场景问题,通过下面的例子我们来看看,如何处理上述场景的问题。

1、       定义观察者接口

//定义一个更新的接口方法给那些在目标发生改变的时候被通知的观察者对象调用

public interface Observer{

// 更新的接口

public voidupdate(WeatherSubject subject);

// 设置观察者名称

public voidsetObserverName(String observerName);

// 获取观察者名称

public String getObserverName();

}

2、       定义目标对象的抽象类

public abstract class WeatherSubject {

// 用来保存注册的观察者对象

public List<Observer> observers = new ArrayList<>();

// 把观察者添加到列表中

public void attach(Observer observer) {

observers.add(observer);

}

// 删除集合中的观察者

public void detach(Observer observer) {

observers.remove(observer);

}

protected abstract voidnotifyObservers();

}

3、       定义目标对象的构造类

public classConcreteWeahterSubject extendsWeatherSubject {

// “晴天” “下雨” “下雪”

// 目标对象的状态

private String weatherContent;

@Override

protected void notifyObservers() {

// TODO Auto-generated method stub

// 循环所有注册的观察者

for (Observer observer : observers) {

/*

* 观察者1 需要“下雨”的条件通知,其他条件不通知

* 观察者2 需要“下雨”或者“下雪”的条件通知,其他条件不通知

* 如果是晴天不通知

*/

// 下雨

if ("下雨".equals(this.getWeatherContent())) {

if ("观察者1".equals(observer.getObserverName())) {

observer.update(this);

}

if ("观察者2".equals(observer.getObserverName())) {

observer.update(this);

}

}

// 下雪

if ("下雪".equals(this.getWeatherContent())) {

if ("观察者2".equals(observer.getObserverName())) {

observer.update(this);

}

}

}

}

public String getWeatherContent() {

return weatherContent;

}

public voidsetWeatherContent(String weatherContent) {

this.weatherContent = weatherContent;

this.notifyObservers();

}

}

4、       定义观察者的构造类

public class ConcreteObserverimplements Observer {

// 观察者的名字

private String observerName;

// 天气内容

private String weatherContent;

// 提醒内容

private String remindContent;

@Override

public voidupdate(WeatherSubject subject) {

// TODO Auto-generated method stub

weatherContent = ((ConcreteWeahterSubject) subject).getWeatherContent();

System.out.println(observerName + "收到了:" + weatherContent + ", " + remindContent);

}

@Override

public voidsetObserverName(String observerName) {

this.observerName = observerName;

}

@Override

public String getObserverName() {

return observerName;

}

public String getWeatherContent() {

return weatherContent;

}

public voidsetWeatherContent(String weatherContent) {

this.weatherContent = weatherContent;

}

public String getRemindContent() {

return remindContent;

}

public voidsetRemindContent(String remindContent) {

this.remindContent = remindContent;

}

}

5、       验证

public class Client {

public static void main(String[] args) {

// TODO Auto-generated method stub

// 创建目标

ConcreteWeahterSubjectweahterSubject = new ConcreteWeahterSubject();

// 创建观察者

ConcreteObserverobserver1 = new ConcreteObserver();

observer1.setObserverName("观察者1");

observer1.setRemindContent("下雨了,今天不适合旅游");

ConcreteObserverobserver2 = new ConcreteObserver();

observer2.setObserverName("观察者2");

observer2.setRemindContent("下雨了,所有室外活动取消");

// 注册观察者

weahterSubject.attach(observer1);

weahterSubject.attach(observer2);

// 更新通知

weahterSubject.setWeatherContent("下雨");

//    weahterSubject.setWeatherContent("下雪");

}

}

6、       运行截图

       

以上就是观察者模式的基本内容,希望小伙伴们提出问题,我们共同探讨。

 

模式的秘密——观察者模式相关推荐

  1. 模式的秘密-观察者模式(二)

    认识观察者模式: 命名建议: 第一:目标接口的定义,建议在名称后面加上Subject. 第二:观察者接口的定义,建议在名称后面跟上Observer. 第三:观察者接口的更新方法,建议名称为Update ...

  2. 模式的秘密-观察者模式(四)

    区别对待观察者场景问题 两点需求: 第一:黄明女朋友只想接收下雨的天气预报. 第二:黄明老妈,想接收下雨或者下雪的天气预报. 解决思路: 情况之一: 如果天气晴天,按照黄明女朋友需要下雨添加,黄明老妈 ...

  3. 商业模式的秘密—leo看赢在中国(3)

    版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出版.作者信息和本声明.否则将追究法律责任.本文地址:http://blog.csdn.net/jobchanceleo/archiv ...

  4. 《JAVA与模式》之观察者模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述观察者(Observer)模式的: 观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式.模型-视图(Mo ...

  5. java---设计模式之:观察者模式

    观察者模式,顾名思义,监视模式,比如xxlistener  ,常用于 监听事件,消息通知等场合: 现在做个简单的例子: 用户1  --------->观察者1 用户2  ---------> ...

  6. php模式设计之 观察者模式

    观察者模式 观察者设计模式能够更便利创建和查看目标对象状态的对象,并且提供和核心对象非耦合的置顶功能性.观察者设计模式非常常用,在一般复杂的WEB系统中,观察者模式可以帮你减轻代码设计的压力,降低代码 ...

  7. java 广播模式_Java设计模式——观察者模式的灵活应用

    灵感来源于一个猪队友给个人题目java 看到这个,我抓住的关键字是:任何子任务失败,要通知全部子任务执行取消逻辑.dom 这不就是消息广播吗?观察者模式!ide 干活 首先是收听者测试 package ...

  8. 【编程思想】【设计模式】【行为模式Behavioral】观察者模式Observer

    Python转载版 https://github.com/faif/python-patterns/blob/master/behavioral/observer.py #!/usr/bin/env ...

  9. 《JavaScript设计模式与开发实践》模式篇(5)—— 观察者模式

    发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知.在 JavaScript 开发中,我们一般用事件模型 来替代传统的发布- ...

最新文章

  1. centos设置固定IP方法
  2. js实现图片上传本地预览
  3. 如何在Python上用jieba库分析TXT文件的词频
  4. 【BZOJ4873】[六省联考2017]寿司餐厅(网络流)
  5. React state和props使用场景
  6. SAP Commerce Accelerator和SAP Spartacus的技术对比
  7. ASP.NET简易教程-页面布局
  8. 饿了么商家电脑版_饿了么企业版荣膺“2020中国十大影响力人力资源品牌”大奖...
  9. VC.【转】窗口置于前台并激活的方法
  10. Focal Loss 和 LightGBM 多分类应用-python实现
  11. 不同类别游戏音效的特点
  12. 计算机考试专业知识题库,专业知识:计算机考试题库练习题
  13. java题电影院售票设计报告_基于Java的电影订票网站的设计实现 任务书.doc
  14. 全国青少年计算机考试官网,全国青少年计算机考试开考 每年组织四次
  15. 剑指Offer读书笔记(持续更新中)
  16. MFC字符串资源IDR_MAINFRAME和IDR_XXXXTYPE
  17. UCSC genome browser 个人track 安装
  18. 30分钟理解关键链--《突破项目的瓶颈--关键链 》读书笔记
  19. hasattr()函数的用法
  20. php中import什么意思,Thinkphp中import的几个用法详细介绍

热门文章

  1. 3dmax归零坐标轴
  2. DirectX12(D3D12)基础教程(十七)——让小姐姐翩翩起舞(3D骨骼动画渲染【6】)
  3. MapGIS67个性化设置快捷键
  4. OpenCV 图片数字识别(C++)
  5. jsonpath - 使用 JSONPath 解析 JSON
  6. 如何快速实现文章AI伪原创?
  7. Unity3D手机游戏原创分享(益智类)
  8. 全球与中国动臂和剪式举升机市场深度研究分析报告
  9. 【芝士】%ff%ff%ff%ff%ff%ff%ff || 0xff0xff0xff0xff0xff0xff0xff 异或,~ 取反过rce 自己应该是弄明白了,
  10. 微信打不开怎么回事?分享解决办法