本文首发于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;
@Override
public void registerObserver(AsgardManObserver asgardManObserver) {
lists.add(asgardManObserver);
}
@Override
public void removeObserver(AsgardManObserver asgardManObserver) {
lists.remove(asgardManObserver);
}
@Override
public void notifyObservers() {
lists.forEach(asgardMan -> asgardMan.update(message));
}
@Override
public 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;
@Override
public 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:我没听懂你说什么");
}
}
// 一旦海姆达尔发送敌人袭击消息,托尔立即采取行动
@Override
public void update(String message) {
this.message = message;
takeAction();
}
}
观察者2:洛基
public class Lokey implements AsgardManObserver,Action {
private String message;
@Override
public 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:我继续做我的闲鱼~");
}
}
@Override
public void update(String message) {
this.message = message;
takeAction();
}
}
观察者3:咸鱼
public class SaltedFish implements AsgardManObserver {
private String message;
@Override
public 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;
@Override
public void registerObserver(AsgardManObserver asgardManObserver) {
lists.add(asgardManObserver);
}
@Override
public void removeObserver(AsgardManObserver asgardManObserver) {
lists.remove(asgardManObserver);
}
@Override
public void notifyObservers() {
// 只要在遍历这里传递this就可以,观察者的update方法需要修改一下
lists.forEach(asgardMan -> asgardMan.update(this));
}
@Override
public void setMessage(String message) {
this.message = message;
System.out.println("Heimdall:"+message);
notifyObservers();
}
}
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()方法中的参数
// 这种就是参数和被观察者饮用都传过去
@Override
public 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个人博客
欢迎转载,转载请注明出处!
参考资料:
原文:https://www.cnblogs.com/cdream-zs/p/10090864.html