观察者模式是日常开发中最为常用的设计模式之一,本文主要分为四个部分:1、什么是观察者模式 2、观察者模式由哪些组件构成 3、使用观察者模式实现天气预报布告栏的案例 4、使用JDK内置的观察者模式实现天气预报布告栏的案例。
什么是观察者模式
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象的状态发生改变时,它的所有依赖者都会收到通知,并且做出改变。
在现实生活中,可以找到很多跟观察者模式有关的例子,比如说猎头和求职者之间就是一种一对多的关系,有一个猎头,求职者们可以在他那儿登陆注册,留存简历,当猎头发现有合适的工作时会将应聘信息发送给这些人,求职者可以随时跟猎头建立或者取消合作关系,一旦取消合作关系,猎头就不会再发送应聘信息给对应的求职者,这其中猎头就扮演一种有状态的对象,而求职者就是依赖猎头的对象,他们是一对多的关系。
观察者模式由哪些组件构成
根据观察者模式的概念,我们很容易想到,观察者模式中的两种核心组件,即主题对象(Subject)和众多的观察者对象(Observer),一旦主题对象中的内容发生改变,观察者也要根据改变的内容对自己做出调整。因此观察者的UML类图可以简单描述为:
上图中,Subject是一个主题接口,接口中包含注册观察者、移除观察者、通知观察者这三个基本事件,每个具体的主题都应该去实现这个接口,重写其中的方法。Observer是观察者接口,与主题是多对一的关系,观察者的核心方法是update方法,根据主题发生的变化,自动更新自己的状态。
观察者模式实现天气预报布告栏案例
通过一个实际的案例来运用一下观察者模式,一个天气预报发布平台有三种布告栏:
- 目前天气布告栏
格式为:今天天气(晴朗/多云/有雨/有雪) - 气压温度布告栏
格式为:今天气压(XX)Pa,温度(XX)摄氏度 - 明天天气预报布告栏
格式为:明天天气(晴朗/多云/有雨/有雪)
这个案例中,我们可以把天气预报的数据看成是主题对象Subject,包含了天气的详细信息,而三个布告栏就是观察者Observers,Subject中天气信息的一旦变更会通知三个布告栏,布告栏调用自己的update方法来更新显示内容,这是一个典型的观察者模式,下面我们就用代码来实现这个案例。
// 主题接口
public interface Subject {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}
// 布告栏显示接口
// 布告栏信息显示,每个布告栏除了实现Observer接口,还需实现本接口
public interface DisplayElement {
public void display();
}
// 观察者接口
public interface Observer {
// 这个方法中的数据,原则上来说应该封装成一个对象,这里为了简单起见直接写死了
public void update(String todayWeather,Double pa, Double temp, String tomorrowWeather);
}
// 天气数据主题类
import java.util.ArrayList;
import java.util.List;
public class WeatherData implements Subject {
private List<Observer> observers;
private String todayWeather;
private Double pa;
private Double temp;
private String tomorrowWeather;
public WeatherData(){
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
// 注册新的观察者
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int index = observers.indexOf(observer);
if(index > -1)
observers.remove(observer);
}
@Override
public void notifyObservers() {
//数据改动时通知各个观察者
for( Observer o : observers)
o.update(todayWeather,pa,temp,tomorrowWeather);
}
public void dataChanged(){
notifyObservers();
}
public void setNewData(String todayWeather,Double pa, Double temp, String tomorrowWeather){
this.todayWeather = todayWeather;
this.pa = pa;
this.temp = temp;
this.todayWeather = tomorrowWeather;
dataChanged();
}
}
// 温度气压布告栏
public class TempAndPressureBoard implements Observer,DisplayElement{
private Double pa;
private Double temp;
private Subject weatherData;
public TempAndPressureBoard(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("气压温度布告栏------》今天气压"+pa+"Pa,温度"+temp+"摄氏度");
}
@Override
public void update(String todayWeather, Double pa, Double temp, String tomorrowWeather) {
this.pa = pa;
this.temp = temp;
display();
}
}
// 今天天气布告栏
public class TodayWeatherBoard implements Observer,DisplayElement {
private String todayWeather;
private Subject weatherData;
public TodayWeatherBoard(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("今天天气布告栏------》今天天气"+this.todayWeather);
}
@Override
public void update(String todayWeather, Double pa, Double temp, String tomorrowWeather) {
this.todayWeather = todayWeather;
display();
}
}
// 测试类
public class Test {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
TodayWeatherBoard todayBoard = new TodayWeatherBoard(weatherData);
TempAndPressureBoard tempAndPressureBoard = new TempAndPressureBoard(weatherData);
weatherData.setNewData("晴朗",5.0,25.0,"晴朗");
weatherData.setNewData("下雨",4.0,22.0,"下雪");
}
}
运行结果
采用JDK内置的观察者模式实现天气预报布告栏案例
上面是我们自己动手实现的一个天气预报布告栏的案例,不过自己实现还有几个地方的细节没有处理好:
- 上面的观察者更新的数据写死了,无法更改。
- 主题对象将所有数据推送给观察者,但是有些数据并不是每个观察者都需要的。那么,主题对象是不是应该只通知观察者数据更新了,而具体需要什么数据,由各个观察者自己去主题对象那边取。
基于以上两个问题,我们可以做出进一步改进,不过这里就不自己动手写了,我们用JDK中封装好的观察者模式编写改进。
// WeatherData 主题类,继承Observable类
import java.util.Observable;
public class WeatherData extends Observable {
private String todayWeather;
private Double pa;
private Double temp;
private String tomorrowWeather;
public void setNewData(String todayWeather,Double pa, Double temp, String tomorrowWeather){
this.todayWeather = todayWeather;
this.pa = pa;
this.temp = temp;
this.todayWeather = tomorrowWeather;
setChanged();
notifyObservers();
}
public String getTodayWeather() {
return todayWeather;
}
public void setTodayWeather(String todayWeather) {
this.todayWeather = todayWeather;
}
public Double getPa() {
return pa;
}
public void setPa(Double pa) {
this.pa = pa;
}
public Double getTemp() {
return temp;
}
public void setTemp(Double temp) {
this.temp = temp;
}
public String getTomorrowWeather() {
return tomorrowWeather;
}
public void setTomorrowWeather(String tomorrowWeather) {
this.tomorrowWeather = tomorrowWeather;
}
}
// TempAndPressureBoard 布告板
import java.util.Observable;
import java.util.Observer;
public class TempAndPressureBoard implements Observer, DisplayElement{
private Double pa;
private Double temp;
public TempAndPressureBoard(Observable observable){
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if( o instanceof WeatherData){
this.pa = ((WeatherData) o).getPa();
this.temp = ((WeatherData) o).getTemp();
display();
}
}
@Override
public void display() {
System.out.println("气压温度布告栏------》今天气压"+pa+"Pa,温度"+temp+"摄氏度");
}
}
// 测试类
public class Test {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
TempAndPressureBoard tempAndPressureBoard = new TempAndPressureBoard(weatherData);
weatherData.setNewData("晴朗",5.0,25.0,"晴朗");
weatherData.setNewData("下雨",4.0,22.0,"下雪");
}
}
运行结果