正文

一、定义

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

要点:

  • 观察者模式定义了对象之间一对多的关系。
  • 观察者模式让主题(可观察者)和观察者之间松耦合。
  • 主题对象管理某些数据,当主题内的数据改变时,会以某种形式通知观察者。
  • 观察者可以订阅(注册)主题,以便在主题数据改变时能收到更新。
  • 观察者如果不想收到主题的更新通知,可以随时取消订阅(注册)。

二、实现步骤

1、创建主题父类/接口

主题父类/接口主要提供了注册观察者、移除观察者、通知观察者三个方法。

/**
* 主题
*/
public class Subject { /**
* 观察者列表
*/
private ArrayList<Observer> observers; public Subject() {
observers = new ArrayList<>();
} /**
* 注册观察者
*/
public void registerObserver(Observer o) {
observers.add(o);
} /**
* 移除观察者
*/
public void removeObserver(Observer o) {
observers.remove(o);
} /**
* 通知所有观察者,并推送数据(也可以不推送数据,而是由观察者过来拉取数据)
*/
public void notifyObservers(Object data) {
for (Observer o : observers) {
o.update(data);
}
}
}

2、创建观察者接口

观察者接口主要提供了更新方法,以供主题通知观察者时调用。

/**
* 观察者接口
*/
public interface Observer { /**
* 根据主题推送的数据进行更新操作
*/
public void update(Object data);
}

3、创建具体的主题,并继承主题父类/实现主题接口

/**
* 主题A
*/
public class SubjectA extends Subject { /**
* 主题数据
*/
private String data; public String getData() {
return data;
} public void setData(String data) {
this.data = data;
// 数据发生变化时,通知观察者
notifyObservers(data);
}
}

4、创建具体的观察者,并实现观察者接口

通过观察者类的构造函数,注册成为主题的观察者。

(1)观察者 A

/**
* 观察者A
*/
public class ObserverImplA implements Observer { private Subject subject; public ObserverImplA(Subject subject) {
// 保存主题引用,以便后续取消注册
this.subject = subject;
// 注册观察者
subject.registerObserver(this);
} @Override
public void update(Object data) {
System.out.println("Observer A:" + data.toString());
}
}

(2)观察者 B

/**
* 观察者B
*/
public class ObserverImplB implements Observer { private Subject subject; public ObserverImplB(Subject subject) {
// 保存主题引用,以便后续取消注册
this.subject = subject;
// 注册观察者
subject.registerObserver(this);
} @Override
public void update(Object data) {
System.out.println("Observer B:" + data.toString());
}
}

5、使用主题和观察者对象

public class Test {

    public static void main(String[] args) {
// 主题
SubjectA subject = new SubjectA();
// 观察者A
ObserverImplA observerA = new ObserverImplA(subject);
// 观察者B
ObserverImplB observerB = new ObserverImplB(subject);
// 模拟主题数据变化
subject.setData("I'm Batman!!!");
subject.setData("Why so serious...");
}
}

三、举个栗子

1、背景

你的团队刚刚赢得一纸合约,负责建立 Weather-O-Rama 公司的下一代气象站——Internet 气象观测站。

该气象站建立在 WeatherData 对象上,由 WeatherData 对象负责追踪目前的天气状况(温度、湿度、气压)。并且具有三种布告板,分别显示目前的状况、气象统计以及简单的预报。当 WeatherData 对象获得最新的测量数据时,三种布告板必须实时更新。

并且,这是一个可扩展的气象站,Weather-O-Rama 气象站希望公布一组 API,好让其他开发人员可以写出自己的气象布告板,并插入此应用中。

2、实现

(1)创建主题父类

/**
* 主题
*/
public class Subject { /**
* 观察者列表
*/
private ArrayList<Observer> observers; public Subject() {
observers = new ArrayList<>();
} /**
* 注册观察者
*/
public void registerObserver(Observer o) {
observers.add(o);
} /**
* 移除观察者
*/
public void removeObserver(Observer o) {
observers.remove(o);
} /**
* 通知所有观察者,并推送数据
*/
public void notifyObservers(float temperature, float humidity, float pressure) {
for (Observer o : observers) {
o.update(temperature, humidity, pressure);
}
}
}

(2)创建观察者接口

/**
* 观察者接口
*/
public interface Observer { /**
* 更新观测值
*/
public void update(float temperature, float humidity, float pressure);
}

(3)创建气象数据类,并继承主题父类

/**
* 气象数据
*/
public class WeatherData extends Subject { /**
* 温度
*/
private float temperature;
/**
* 湿度
*/
private float humidity;
/**
* 气压
*/
private float pressure; public void measurementsChanged() {
// 观测值变化时,通知所有观察者
notifyObservers(temperature, humidity, pressure);
} /**
* 设置观测值(模拟观测值变化)
*/
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}

(4)创建布告板,并实现观察者接口

/**
* 目前状态布告板
*/
public class CurrentConditionsDisplay implements Observer { private Subject weatherData;
private float temperature;
private float humidity; public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
// 注册观察者
weatherData.registerObserver(this);
} @Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
} public void display() {
System.out.println("Current conditions:" + temperature + "F degress and " + humidity + "% humidity");
}
}
/**
* 统计布告板
*/
public class StatisticsDisplay implements Observer { private Subject weatherData;
private ArrayList<Float> historyTemperatures; public StatisticsDisplay(Subject weatherData) {
this.weatherData = weatherData;
// 注册观察者
weatherData.registerObserver(this);
historyTemperatures = new ArrayList<>();
} @Override
public void update(float temperature, float humidity, float pressure) {
this.historyTemperatures.add(temperature);
display();
} public void display() {
if (historyTemperatures.isEmpty()) {
return;
}
Collections.sort(historyTemperatures);
float avgTemperature = 0;
float maxTemperature = historyTemperatures.get(historyTemperatures.size() - 1);
float minTemperature = historyTemperatures.get(0);
float totalTemperature = 0;
for (Float temperature : historyTemperatures) {
totalTemperature += temperature;
}
avgTemperature = totalTemperature / historyTemperatures.size();
System.out.println("Avg/Max/Min temperature:" + avgTemperature + "/" + maxTemperature + "/" + minTemperature);
}
}
/**
* 预测布告板
*/
public class ForecastDisplay implements Observer { private Subject weatherData;
private float temperature;
private float humidity;
private float pressure; public ForecastDisplay(Subject weatherData) {
this.weatherData = weatherData;
// 注册观察者
weatherData.registerObserver(this);
} @Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
} public void display() {
System.out.println("Forecast:waiting for implementation...");
}
}

(5)测试

public class Test {

    public static void main(String[] args) {
// 气象数据
WeatherData weatherData = new WeatherData();
// 目前状态布告板
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
// 统计布告板
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
// 预测布告板
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); // 模拟气象观测值变化
weatherData.setMeasurements(80, 65, 30.4F);
weatherData.setMeasurements(82, 70, 29.2F);
weatherData.setMeasurements(78, 90, 29.2F);
}
}

《Head First 设计模式》:观察者模式的更多相关文章

  1. java设计模式--观察者模式(Observer)

    java设计模式--观察者模式(Observer) java设计模式--观察者模式(Observer) 观察者模式的定义: 定义对象间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖于它的 ...

  2. Java 设计模式系列(十五)观察者模式(Observer)

    Java 设计模式系列(十五)观察者模式(Observer) Java 设计模式系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Java ...

  3. php 设计模式之观察者模式(订阅者模式)

    php 设计模式之观察者模式 实例 没用设计模式的代码,这样的代码要是把最上面那部分也要符合要求加进来,就要修改代码,不符合宁增不改的原则 介绍 观察者模式定义对象的一对多依赖,这样一来,当一个对象改 ...

  4. [JS设计模式]:观察者模式(即发布-订阅者模式)(4)

    简介 观察者模式又叫发布---订阅模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知. 举一个现实生活中的例子,例如小 ...

  5. 设计模式学习-使用go实现观察者模式

    观察者模式 定义 适用场景 优点 缺点 代码实现 不同场景的实现方式 观察模式和发布订阅模式 参考 观察者模式 定义 观察者模式(Observer Design Pattern)定义了一种一对多的依赖 ...

  6. 说说设计模式~大话目录(Design Pattern)

    回到占占推荐博客索引 设计模式(Design pattern)与其它知识不同,它没有华丽的外表,没有吸引人的工具去实现,它是一种心法,一种内功,如果你希望在软件开发领域有一种新的突破,一个质的飞越,那 ...

  7. [Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  8. [Head First设计模式]一个人的平安夜——单例模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  9. [Head First设计模式]抢票中的设计模式——代理模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  10. [Head First设计模式]面向对象的3特征5原则

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

随机推荐

  1. 基本的bash shell 命令

    1.遍历目录:cd 2.显示目录列表:ls 3.创建文件:touch 4.复制文件:cp 5.链接文件:ln 6.重命名文件:mv 7.删除文件:rm 8.创建目录:mkdir 9.删除目录:rmdi ...

  2. markdown分页导出pdf

    在需要分页之处,插入代码: <div STYLE="page-break-after: always;"></div>

  3. Java 设置Excel单元格格式—基于Spire.Cloud.SDK for Java

    本文介绍使用Spire.Cloud.SDK for Java来设置Excel单元格格式,包括字体.字号.单元格背景.字体下滑线.字体加粗.字体倾斜.字体颜色.单元格对齐方式.单元格边框等 一.下载SD ...

  4. 包子凑数(dp 0-1、完全背包)【背包问题】

    包子凑数(蓝桥杯) 感谢:@ Statusrank 题目链接(点击) 题目描述 小明几乎每天早晨都会在一家包子铺吃早餐.他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子.每种蒸笼都有非常多 ...

  5. tcpdump使用和抓包分析

    参考资料: http://www.cnblogs.com/ggjucheng/archive/2012/01/14/2322659.html tcpdump可以将网络中传送的数据包的“头”完全截获下来 ...

  6. 使用ansible实现批量免密认证

    一.目的 批量实现免密认证,适合管理大批量机器使用 二.步骤 1-1.第一种方式:收集被控制主机的公钥,用于构建并验证ssh_known_hosts # ssh-keyscan 10.246.151. ...

  7. mingw32 exception在sjlj与dwarf差别-反汇编分析

    sjlj (setjump/longjump)与dwarf-2为mingw32两种异常处理模型的实现.sjlj有着开销,而随linux发行的mingw32开发库包都是用sjlj版编译的,而Qt却采用d ...

  8. VS Code WebApi系列——1、配置

    Knowledge should be Shared in Free. 最近在研究VS code下的webapi,看了很多文档,还是微软官方的例子好,不过不太适应国人习惯,所以写点东西. 直接了当 开 ...

  9. ca13a_c++_顺序容器的操作6删除元素

    /*ca13a_c++_顺序容器的操作6删除元素c.erase(p) //删除迭代器p指向的位置c.erase(b,e) //删除b to e之间的数据,迭代器b包括,e不包括c.clear()//删 ...

  10. Java基础-Java中transient有什么用-序列化有那几种方式

    此文转载于知乎的一篇文章,看着写的非常全面,分享给大家. 先解释下什么是序列化 我们的对象并不只是存在内存中,还需要传输网络,或者保存起来下次再加载出来用,所以需要Java序列化技术. Java序列化 ...