观察者模式
这篇博文是根据B站尚硅谷设计模式课程韩老师所讲而做的,在此表示感谢!
观察者模式:对象之间多对一依赖的一种设计方案,
被依赖的对象为 Subject
,
依赖的对象为 Observer
,
Subject通知 Observer 变化,比如奶站\气象局\报社等就类似于 Subject,Subject是 1 的一方,Observer是用户是多的一方;
基本介绍:
- 观察者模式类似 订奶\订报\天气平台等业务
- Subject:主要是登记注册、移除和通知
Observer(观察者)
,主要有如下方法:- registerObserver() 注册观察者
- removeObserver() 移除观察者
- notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送, 看具体需求定;
- Observer:观察者,主要接收Subject的通知;
案例介绍
我们通过如下一个案例来演示观察者模式,问题如下:
- 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方);
- 需要设计开放型 API,便于其他第三方也能接入气象站获取数据;
- 提供温度、气压和湿度的接口;
- 测量数据更新时,要能实时的通知给第三方;
UML
编码
编写观察者的接口类
观察者的接口类Observer
如下所示:
package edu.hebeu.observer.observer;
/**
* 观察者接口
* @author 13651
*
*/
public interface Observer {
void update(float temperature, float pressure, float humidity);
}
编写管理观察者的接口
管理观察者的接口Subject
,包括了对观察者的注册,删除,通知等方法,如下
package edu.hebeu.observer.subject;
import edu.hebeu.observer.observer.Observer;
/**
*
* @author 13651
*
*/
public interface Subject {
/**
* 注册观察者
* @param observer
*/
void registerObserver(String observerName, Observer observer);
/**
* 删除观察者
* @param observer
*/
void removeObserver(String observerName);
/**
* 通知所有的观察者
*/
void notifyObservers();
}
编写能够测量、修改天气数据的类
该类嗨需要实现Subect接口
,使该类能够获得其内部的特性,如,注册、删除、通知观察者,以保证在每次数据更新时数据能实时的发送至观察者,让观察者将最新的数据展示,代码如下:
package edu.hebeu.observer.subject;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import edu.hebeu.observer.observer.Observer;
public class WeatherData implements Subject{
// 温度,气压,湿度
private float temperatrue;
private float pressure;
private float humidity;
/**
* 观察者集合
*/
private Map<String, Observer> observers;
public WeatherData() {
observers = new HashMap<>();
}
/**
* 获取温度
* @return
*/
public float getTemperature() {
return temperatrue;
}
/**
* 获取气压
* @return
*/
public float getPressure() {
return pressure;
}
/**
* 获取湿度
* @return
*/
public float getHumidity() {
return humidity;
}
/**
* 当数据有更新时,调用的函数
* @param temperature
* @param pressure
* @param humidity
*/
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
//调用 notifyObservers, 将最新的信息 推送给 接入方 所有的观察者
notifyObservers();
}
@Override
public void registerObserver(String observerName, Observer observer) {
observers.put(observerName, observer);
System.out.println("添加《" + observerName + "》观察者成功!");
}
@Override
public void removeObserver(String observerName) {
if(observers.containsKey(observerName)) {
observers.remove(observerName);
System.out.println("删除《" + observerName + "》观察者成功!");
return;
}
System.err.println("未找到《" + observerName + "》观察者,删除失败!");
}
@Override
public void notifyObservers() {
Set<Map.Entry<String, Observer>> observerSet = observers.entrySet();
for(Map.Entry<String, Observer> observer : observerSet) {
observer.getValue().update(this.temperatrue, this.pressure, this.humidity);
}
}
}
编写"第三方的接口“
实际上就类似于MySQL、Oracle实现Java提供的数据库驱动一样,我们为了模拟需要提供如下的几个类:
BaiDu
package edu.hebeu.observer.observer;
public class BaiDu implements Observer{
// 温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
/**
* 更新天气情况,是由 WeatherData的对象实例来调用,即使用推送模式
* @param temperature
* @param pressure
* @param humidity
*/
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
/**
* 显示数据
*/
public void display() {
System.out.println("========================百度天气=========================");
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure + "***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}
WangYi
package edu.hebeu.observer.observer;
public class WangYi implements Observer{
// 温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
/**
* 更新天气情况,是由 WeatherData的对象实例来调用,即使用推送模式
* @param temperature
* @param pressure
* @param humidity
*/
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
/**
* 显示数据
*/
public void display() {
System.out.println("========================网易天气=========================");
System.out.println("&&&Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure + "---");
System.out.println("^^^Today mHumidity: " + humidity + "***");
}
}
XinLang
package edu.hebeu.observer.observer;
public class XinLang implements Observer{
// 温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
/**
* 更新天气情况,是由 WeatherData的对象实例来调用,即使用推送模式
* @param temperature
* @param pressure
* @param humidity
*/
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
/**
* 显示数据
*/
public void display() {
System.out.println("========================新浪天气=========================");
System.out.println("---Today mTemperature: " + temperature + "---");
System.out.println("***Today mPressure: " + pressure + "***");
System.out.println("+++Today mHumidity: " + humidity + "+++");
}
}
可以发现我们需要什么订阅者,直接就实现Observer接口
就好了,非常方便;
编写测试类
编写上述代码的测试类Client
,如下所示:
package edu.hebeu.observer;
import java.util.Scanner;
import edu.hebeu.observer.observer.BaiDu;
import edu.hebeu.observer.observer.WangYi;
import edu.hebeu.observer.observer.XinLang;
import edu.hebeu.observer.subject.WeatherData;
public class Client {
private static float TEMPERATURE, PRESSURE, HUMIDITY; // 声明保持温度、湿度、气压的静态变量
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
// 注册初始的观察者
weatherData.registerObserver("百度天气", new BaiDu());
weatherData.registerObserver("新浪天气", new XinLang());
weatherData.registerObserver("网易天气", new WangYi());
// 更新信息
weatherData.setData(37f, 158.9f, 29f);
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println();System.out.println();System.out.println();
System.out.println("\"u(update)\"更新数据");
System.out.println("\"a(add)\"添加观察者");
System.out.println("\"r(remove)\"删除观察者");
System.out.println("\"e(exit)\"退出程序");
System.out.print("请输入:");Character keyword = scanner.next().charAt(0);
if(keyword.equals('u')) {
System.out.print("温度:"); TEMPERATURE = scanner.nextFloat();
System.out.print("湿度:"); PRESSURE = scanner.nextFloat();
System.out.print("气压:"); HUMIDITY = scanner.nextFloat();
System.out.println("--------------------------------------------天气更新-------------------------------");
weatherData.setData(TEMPERATURE, PRESSURE, HUMIDITY); // 改变数据
} else if(keyword.equals('a')) {
System.out.print("请输入添加的观察者名:"); String observerName = scanner.next();
if(observerName.equals("百度天气")) {
weatherData.registerObserver(observerName, new BaiDu());
} else if(observerName.equals("新浪天气")) {
weatherData.registerObserver(observerName, new XinLang());
} else if(observerName.equals("网易天气")) {
weatherData.registerObserver(observerName, new WangYi());
} else {
System.err.println("第三方库中未找到" + observerName + "接口,添加失败!");
}
} else if(keyword.equals('r')) {
System.out.print("请输入删除的观察者名:"); String observerName = scanner.next();
weatherData.removeObserver(observerName);
} else if(keyword.equals('e')) { // 如果输入是 "exit"
break; // 退出循环
}
}
if(scanner != null) {
scanner.close();
}
System.out.println("bye~~");
}
}
测试
测试更新天气数据,就会自动的通知其他的观察者改变它们的数据,然后将最新的数据显示出来,如下所示:
也可以将某些注册的观察者删除,如下:
也可以添加观察者,但是要保证有”该观察者“,如下: