设计模式——观察者模式(海姆达尔与仙宫人民)
本文首发于cdream的个人博客,点击获得更好的阅读体验!
欢迎转载,转载请注明出处
本文主要对观察者进行概述讲解,并使用观察者模式来模拟海姆达尔在发现敌人来袭后通知雷神托尔和洛基的过程。
一、概念
定义
观察者模式也叫作发布-订阅模式,也就是事件监听机制。观察者模式定义了对象之间的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己,并采取相应活动。
主要结构
- 抽象主题角色
被观察者,把所有观察者对象引用保存在一个集合里,对外提供增加删除的接口
- 具体主题角色
将有关状态存入具体观察者对象,在内部状态发生改变时给所有注册过的观察者发送信息
- 抽象观察者角色
为所有具体观察者定义更新接口
- 具体观察者角色
储存与主题状态自恰的状态,实现更新接口,与主题状态协调
UML类图:
特点:松耦合
- 观察者只知道观察者实现了某个接口,并不需要知道观察者具体类,实现了哪些细节。
- 任何时候可以增加观察者。主题唯一依赖的东西是储存观察者实现的列表。所以在运行时添加新的观察者也不会对主题造成影响。
- 有新的观察者加入时,主题代码不需要改变。主题在意的只是发送通知给观察者。
- 可以独立的使用主题和观察者,因为他们之间是松耦合的。
- 改变主题或观察者其一并不需要修改另一方,只要遵守接口就可以。
二、海达姆斯与仙宫人民
海姆达尔(Heimdall)是希芙的哥哥。他是能眼视万物和耳听一切的仙宫守护哨兵,他站在彩虹桥比弗罗斯特上,并注意观察任何对仙宫的袭击。他作为仙宫的守卫站立着,保卫这个城市的大门使任何闯入者远离,是奥丁最为信任的仆人之一。
对于仙宫的住民来说Heimdall是他们的可观察者,当海姆达尔观察到危机,会向所有他需要通知的人发送通知。现在我们用观察者模式来模拟海姆达斯发现危机通知仙宫住民这种情况。
下面是一个UML图,实现了海姆达斯与仙宫人民的解耦,去除强耦合关系,同时哨兵接口可以给仙宫所有哨兵使用,观察者们根据需要实现Action接口
定义哨兵接口
/*** 阿斯加德所有的哨兵都有的行为* @author cdream* @date 2018/12/9*/
public interface Sentinel {/*** 注册需要通知的阿斯加德人* @param asgardManObserver*/void registerObserver(AsgardManObserver asgardManObserver);/*** 移除需要通知的阿斯加德人* @param asgardManObserver*/void removeObserver(AsgardManObserver asgardManObserver);/*** 通知所有asgard人*/void notifyObservers();/*** 观察到了信息* @param message*/void setMessage(String message);
}
海姆达斯实现哨兵接口
/*** 海姆达尔类,他向仙宫内的人民来传递信息,是仙宫内人民的看观察者* @author cdream* @date 2018/12/9*/
public class Heimdall implements Sentinel {// 维系所有需要通知的人,这是观察者和被观察者唯一关联的地方private ArrayList<AsgardManObserver> lists=new ArrayList<>();private String message;@Overridepublic void registerObserver(AsgardManObserver asgardManObserver) {lists.add(asgardManObserver);}@Overridepublic void removeObserver(AsgardManObserver asgardManObserver) {lists.remove(asgardManObserver);}@Overridepublic void notifyObservers() {lists.forEach(asgardMan -> asgardMan.update(message));}@Overridepublic void setMessage(String message) {this.message = message;System.out.println("Heimdall:"+message);notifyObservers();}
}
AsgardManObserver接口,所有的想接收信息的人都要实现
public interface AsgardManObserver {/*** 接收来自海姆达尔的信息,并更新状态* @param message*/void update(String message);
}
Action接口,需要采取行动的人实现
public interface Action {// 采取行动void takeAction();
}
两个需要用到的常量
// 灭霸
public static final String THANOS="Thanos";
// 冰霜巨人
public static final String FROST_GIANTS="Frost Giants";
观察者1:雷神托尔
public class Thor implements AsgardManObserver,Action {private String message;@Overridepublic void takeAction() {// 如果是灭霸if (message!=null && message.contains(Const.THANOS)){System.out.println("Thor:准备对抗灭霸");// 如果是冰霜巨人}else if(message !=null && message.contains(Const.FROST_GIANTS)){System.out.println("Thor:准备对抗冰霜巨人");}else{System.out.println("Thor:我没听懂你说什么");}}// 一旦海姆达尔发送敌人袭击消息,托尔立即采取行动@Overridepublic void update(String message) {this.message = message;takeAction();}
}
观察者2:洛基
public class Lokey implements AsgardManObserver,Action {private String message;@Overridepublic void takeAction() {// 如果是灭霸if (message!=null && message.contains(Const.THANOS)){System.out.println("Lokey:准备逃走");// 如果是爸爸}else if(message !=null && message.contains(Const.FROST_GIANTS)){System.out.println("Lokey:准备反叛");}else{System.out.println("Lokey:我继续做我的闲鱼~");}}@Overridepublic void update(String message) {this.message = message;takeAction();}
}
观察者3:咸鱼
public class SaltedFish implements AsgardManObserver {private String message;@Overridepublic void update(String message) {this.message = message;System.out.println("闲鱼:继续做咸鱼");}
}
开始模拟:
public class Test {public static void main(String[] args) {// 仙宫建立,阿斯加德人诞生Sentinel heimdall = new Heimdall();AsgardManObserver thor = new Thor();AsgardManObserver lokey = new Lokey();AsgardManObserver saltedFish = new SaltedFish();// 三个人都去海尔达姆那里去注册heimdall.registerObserver(thor);heimdall.registerObserver(lokey);heimdall.registerObserver(saltedFish);// 冰霜巨人来袭heimdall.setMessage(Const.FROST_GIANTS + "来袭");// 洛基叛变,海达姆斯不再通知洛基heimdall.removeObserver(lokey);System.out.println("-------------");//灭霸来袭heimdall.setMessage(Const.THANOS + "来袭");}
}
结果:
Heimdall:Frost Giants来袭
Thor:准备对抗冰霜巨人
Lokey:准备反叛
闲鱼:继续做咸鱼
-------------
Heimdall:Thanos来袭
Thor:准备对抗灭霸
闲鱼:继续做咸鱼
对抗冰霜巨人一战,洛基叛变,海姆达尔不在向其发送通知~
这是一个典型的观察者模式的推模式,海姆达尔一旦得到敌人来袭的消息就会通知他所维系的观察者,海尔达姆与仙宫住民是松耦合的,都可以独立行动,又不用关注各自的细节(所以他也不知道洛基得到消息后会做什么:)),新来了观察者直接维系到list里扩展性强。
拉模式:将整个被观察者对象引用送给观察者,由观察者获取需要的信息。如下,注意notifyObservers方法的修改
/*** 海姆达尔类,他向仙宫内的人民来传递信息,是仙宫内人民的看观察者* @author cdream* @date 2018/12/9*/
public class Heimdall implements Sentinel {// 维系所有需要通知的人,这是观察者和被观察者唯一关联的地方private ArrayList<AsgardManObserver> lists=new ArrayList<>();private String message;@Overridepublic void registerObserver(AsgardManObserver asgardManObserver) {lists.add(asgardManObserver);}@Overridepublic void removeObserver(AsgardManObserver asgardManObserver) {lists.remove(asgardManObserver);}@Overridepublic void notifyObservers() {// 只要在遍历这里传递this就可以,观察者的update方法需要修改一下lists.forEach(asgardMan -> asgardMan.update(this));}@Overridepublic void setMessage(String message) {this.message = message;System.out.println("Heimdall:"+message);notifyObservers();}
}
1.推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
2.推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
3.拉模式会使观察者获取所有被观察者信息,同时可能会多次获取才能会得到需要的所有信息
4.但是想象其实,拉模式也是推模式的一种,只不过是推送个引用过去,里面包含了更多的信息
三、jdk对观察者模式的支持
jdk本身提供了对观察者模式的支持,并且支持推、拉两种方案。主要类或接口是java.util.Observable(抽象类)和java.util.Observer(接口)
这里就举个Head First 设计模式的一个例子吧,天气数据和天气显示器。不同的天气显示器会显示不同的信息。
用来储存天气信息的实体类
public class WeatherPojo {// 温度private float temperature;// 湿度private float humidity;// 气压private float pressure;public WeatherPojo() {}public WeatherPojo(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;}public float getTemperature() {return temperature;}public void setTemperature(float temperature) {this.temperature = temperature;}public float getHumidity() {return humidity;}public void setHumidity(float humidity) {this.humidity = humidity;}public float getPressure() {return pressure;}public void setPressure(float pressure) {this.pressure = pressure;}
}
被观察者:天气数据
public class WeatherData extends Observable {private float temperature;private float humidity;private float pressure;public WeatherData() {}public void measurementsChanged(){// 这个是observable中的标志,使类更加灵活// 想象天气变个0.01度你都提醒是吧是烦死了// 这里进行限制,达到一定条件再发送通知setChanged();notifyObservers();}public void updateData(WeatherPojo pojo){this.temperature = pojo.getTemperature();this.humidity = pojo.getHumidity();this.pressure = pojo.getPressure();// 被观察者数据发生了改变,提醒观察者measurementsChanged();}public float getTemperature() {return temperature;}public float getHumidity() {return humidity;}public float getPressure() {return pressure;}
}
观察者:天气显示板
public class CurrentDisplay implements Observer {Observable observable;private float temperature;private float humidity;public CurrentDisplay(Observable observable) {this.observable = observable;observable.addObserver(this);}public void display(){System.out.println("温度是:"+temperature+"; 湿度是:"+humidity);}// 注意这里有两个参数,前面是传递观察者对象// 后面可以传递需要的参数,是notifyObservers()方法中的参数// 这种就是参数和被观察者饮用都传过去@Overridepublic void update(Observable o, Object arg) {if (o instanceof WeatherData){WeatherData weatherData = (WeatherData) o;this.temperature = weatherData.getTemperature();this.humidity = weatherData.getHumidity();display();}}
}
jdk对观察者模式的实现需要被观察者继承Observable,对代码有一定的侵入,例如如果海姆达尔还要继承阿斯加德人这个类,那就需要我们手动来实现观察者模式了。
四、总结
观察者模式是比较常见的设计模式,我们常见的MVC就是标准的观察者模式,如果感兴趣看以google"使用观察者模式实现mvc"。此外向消息队列的发布订阅模式也是使用的观察者模式,而且是异步的性能更好,像我们上面实现的这种简单遍历,如果观察者实现复杂那性能看就会受到影响,毕竟要等待一个观察者执行完才能通知下一个观察者。
本文首发于cdream个人博客
欢迎转载,转载请注明出处!
参考资料:
- Head First 设计模式,Eric Freeman &Elisabeth Freeman with Kathy Sierra & Bert Bates
- java设计模式精讲 Debug 方式+内存分析
- 《JAVA与模式》之观察者模式
转载于:https://www.cnblogs.com/cdream-zs/p/10090864.html
设计模式——观察者模式(海姆达尔与仙宫人民)相关推荐
- 计算机学院迎新晚会集宁,迎新晚会 | 信息管理学院2017年“海姆达尔之眼”迎新晚会圆满成功...
原标题:迎新晚会 | 信息管理学院2017年"海姆达尔之眼"迎新晚会圆满成功 北京时间2017年12月8日傍晚19点整,张心瑜小剧场,信息管理学院2017年"海姆达尔之眼 ...
- 元宇宙黑马来袭 海姆达尔Heimdallr 开启链游新玩法
随着诸多链游的崛起,元宇宙链游板块也再次成为币圈热点话题,虽然游戏种类繁多,但目前的链游玩法上却都大同小异,众多链游在把精力投入在机制上,但是却忽略了链游本身的娱乐性,这让原本既能娱乐又能赚钱得游戏变 ...
- PHP面向对象设计模式-姜海强-专题视频课程
PHP面向对象设计模式-21631人已学习 课程介绍 本系列教程内容涵盖PHP常用的设计模式,旨在指导读者搭建易扩展的项目框架,高服用的代码,大程度的发挥面向对象思想的优势. 课程收益 ...
- Python设计模式-观察者模式
Python设计模式-观察者模式 代码基于3.5.2,代码如下; #coding:utf-8 # 观察者设计模式class observerInterface():def update(self,va ...
- [Head First设计模式]山西面馆中的设计模式——观察者模式
原文:[Head First设计模式]山西面馆中的设计模式--观察者模式 系列文章 [Head First设计模式]山西面馆中的设计模式--装饰者模式 引言 不知不自觉又将设计模式融入生活了,吃个饭也 ...
- java设计模式--观察者模式(Observer)
java设计模式--观察者模式(Observer) java设计模式--观察者模式(Observer) 观察者模式的定义: 定义对象间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖于它的 ...
- 【学习笔记】ABAP OOD设计模式 - 观察者模式
ABAP OOD设计模式 - 观察者模式 整理转自-<SAP ABAP 面向对象程序设计(原则.模式及实践)> 定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,这个对象相关依赖的 ...
- Java设计模式-观察者模式(订阅发布模式)
Java设计模式-观察者模式(订阅发布模式) 一起来看 会了就当复习丫,不会来一起来看看吧. 很喜欢一句话:"八小时内谋生活,八小时外谋发展". 如果你也喜欢,让我们一起坚持吧!! ...
- 信号与系统奥本海姆第二版_【中山大学电通信通信号与系统考研】自编的两张小卡片带大家整理一下《奥本海姆·信号与系统》的知识架构...
建议有时间的同学看看以下的视频,讲解的比文章详细些. [中山大学信号与系统考研]<奥本海姆·信号与系统>教材读不下去?抓不住重点?自编的两张小卡片带大家整理一下知识架构.考研的,本科学习的 ...
- 设计模式---观察者模式介绍与理解
设计模式---观察者模式介绍与理解: 观察者模式原理:类似于定牛奶业务 1. 奶站,subject:登记注册,移除,通知(register,remove,notify) 2. 用户,observer: ...
最新文章
- HAOI2011 Problem b
- 精讲 MySQL 事务日志:redo log 和 undo log
- beautifulsoup_Python爬虫-BeautifulSoup
- MVC webuploader 图片
- 一个java源文件中可以声明多少个class与编译后会生成多少个字节码文件
- Linux学习系列之Linux入门(一)linux安装与入门
- 菜鸟的Xamarin.Forms前行之路——绪言
- 单片机c语言必背代码_最适合单片机编程的高级语言,除了C语言,别无选择!...
- DAM的内涵正在改变
- 读大师的书 说自己的话——《传世经典书丛评注版》邀你来点评
- 轻量级网络模型之EfficientNet
- Python数据分析(二): Numpy技巧 (3/4)
- 身份证归属地查询免费api接口代码
- java 调用 pb dll_[转载]一个java调用delphi写的dll问题,郁闷了一天一晚解决
- 修复steam服务器失败怎么办,steam服务器失败
- 站在巨人的肩膀上--邵泓鑫
- PCM开发板模块实验指导--有刷直流马达速度控制实验
- Origin | 一个X对应多个Y的折线图
- Android显示MP3专辑封面
- 【数据库】MySQL的sql语句详解