2、14观察者模式
定义
观察者模式也叫做发布订阅模式,定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的(所有)对象都会得到通知并被自动更新。
以松耦合方式在一系列对象之间沟通状态。
观察者模式的代表-MVC。
通用类图
1)被观察者Subject
定义被观察者必须实现的职责,它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
2)具体被观察者/主题ConcreteSubject
将有关状态存入具体观察者对象;
在具体主题的内部状态改变时,给所有登记过的观察者发出通知;
定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
具体主题也可能有设置和获取状态的方法。
3)(抽象)观察者Observer
观察者接到消息后,即进行update(更新)操作,对接收到的信息进行处理;
为所有的具体观察者定义一个接口,在得到主题的通知时更新自己;
这个接口有时也叫做更新接口。
4)具体观察者ConcreteObserver
每个观察者在接收到消息后的处理反应是不同的,各个观察者有自己的处理逻辑;
实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。
案例
import java.util.Vector;
/**
* 场景类
*/
public class Client1 {
public static void main(String[] args) {
//创建一个被观察者
ConcreteSubject subject = new ConcreteSubject();
//定义一个观察者
Observer observer = new ConcreteObserver();
//观察者观察被观察者
subject.addObserver(observer);
//观察者开始活动了
subject.notifyObservers();
}
}
/**
* 观察者
*/
interface Observer{
//更新方法
void update();
}
/**
* 具体观察者
*/
class ConcreteObserver implements Observer{
//实现更新方法
@Override
public void update() {
System.out.println("接收到信息,并进行处理.");
}
}
/**
* 被观察者
*/
abstract class Subject{
//定义一个观察者数组
private Vector<Observer> observers = new Vector<>();
//增加一个观察者
public void addObserver(Observer o){
this.observers.add(o);
}
//删除一个观察者
public void delObserver(Observer o){
this.observers.remove(o);
}
//通知所有的观察者
public void notifyObservers(){
for(Observer o : this.observers){
o.update();
}
}
}
/**
* 具体被观察者
*/
class ConcreteSubject extends Subject{
//具体的业务
public void doSomething(){
super.notifyObservers();
}
}
业务案例1(推/push数据,相对更”正确”)
import java.util.ArrayList;
public class Client2 {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(11f,11f,11f);
weatherData.setMeasurements(22f,22f,22f);
weatherData.setMeasurements(33f,33f,33f);
}
}
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
interface Observer {
//当气象观测值改变时,主题会把这些状态值当做方法的参数,传送给观察者.
//这里并不是最好的办法,因为这些观测值的种类和个数在未来有可能改变.
void update(float temperature,float humidity,float pressure);
}
interface DisplayElement {
void display();
}
class WeatherData implements Subject{
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
this.observers = new ArrayList();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i >= 0){
observers.remove(o);
}
}
@Override
public void notifyObservers() {
for (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer) observers.get(i);
observer.update(temperature,humidity,pressure);
}
}
public void measurementsChanged(){
notifyObservers();
}
public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
class CurrentConditionsDisplay implements Observer,DisplayElement{
private float temperature;
private float humidity;
private Subject weatherData;
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();
}
@Override
public void display() {
System.out.println("CurrentConditionsDisplay{" +
"temperature=" + temperature +
", humidity=" + humidity +
'}');
}
}
输出结果:
CurrentConditionsDisplay{temperature=11.0, humidity=11.0}
CurrentConditionsDisplay{temperature=22.0, humidity=22.0}
CurrentConditionsDisplay{temperature=33.0, humidity=33.0}
业务案例2(拉/pull数据)
import java.util.Observable;
import java.util.Observer;
public class Client3 {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(11f,11f,11f);
weatherData.setMeasurements(22f,22f,22f);
weatherData.setMeasurements(33f,33f,33f);
}
}
class WeatherData extends Observable{
private float temperature;
private float humidity;
private float pressure;
/**
* 构造器不再需要为了记住观察者们而建立数据结构了.
* 不再需要追踪观察者了,也不需要管理注册与删除.
* 所以与Client2相比集合删除了、注册、添加、通知的相关代码也都删除了.
*/
public WeatherData() {
}
public void measurementsChanged(){
//在调用notifyObservers()之前记得先调用setChanged()来指示状态已经改变.
setChanged();
//没有调用notifyObservers()传送数据对象,这表示现在采用的做法是"拉".
notifyObservers();
}
public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
/**
* 下述三个方法并不是新方法,只是因为要使用"拉"的做法,才提醒有这些方法.
* 观察者会利用下述三个方法取得WeatherData对象的状态.
* @return
*/
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
interface DisplayElement {
void display();
}
class CurrentConditionsDisplay implements Observer,DisplayElement{
private Observable observable;
private float temperature;
private float humidity;
/**
* 现在构造器需要一个Observable当参数,并将CurrentConditionsDisplay对象登记为观察者.
* @param observable
*/
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void update(Observable obs, Object arg) {
if(obs instanceof WeatherData){
WeatherData weatherData = (WeatherData) obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
@Override
public void display() {
System.out.println("CurrentConditionsDisplay{" +
"temperature=" + temperature +
", humidity=" + humidity +
'}');
}
}
输出结果:
CurrentConditionsDisplay{temperature=11.0, humidity=11.0}
CurrentConditionsDisplay{temperature=22.0, humidity=22.0}
CurrentConditionsDisplay{temperature=33.0, humidity=33.0}
优点
1)观察者和被观察者之间是抽象耦合
2)建立一套触发机制
观察者模式可以完美地实现链条形式。
缺点
开发效率和运行效率问题:
1)开发和调试非常复杂。
2)在java中的消息默认是顺序执行,一个观察者卡壳,会影响整理的执行效率,在这种情况下,一般考虑使用异步的方式。
3)多级触发时的效率问题。
使用场景
1)关联行为场景。关联行为是可拆分的,而不是”组合”关系。
2)事件多级触发场景。
3)跨系统的消息交换场景,如消息队列的处理机制。
注意事项
1)广播链的问题
一个观察者可以有双重身份,既是观察者,也是被观察者,但是链一旦建立,逻辑就比较复杂,可维护性非常差,根据经验,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),这还是比较好控制的。
2)异步处理的问题
异步处理需要考虑线程安全和队列问题。
3)和责任链的区别
观察者模式和责任链模式的最大区别就是观察者广播链在传播的过程中消息是随时更改的,它是由相邻的连个节点协商的消息结构;而责任链模式在消息传递过程中基本上保持消息不可变,如果要改变,也只是在原有的消息上进行修正。
项目中真实的观察者模式
1)观察者和被观察者之间的消息沟通
被观察者状态改变会触发观察者的一个行为,同时会传递一个消息给观察者。
观察者中的update方法接收两个参数,一个是被观察者,一个是DTO,DTO由被观察者生成,由观察者消费。
2)观察者响应方式
观察者如何快速响应?
一是采用多线程技术(异步架构),甭管是备观察者启动线程还是观察者启动线程,都可以明显地提高系统性能。
二是缓存架构(同步架构),准备足够的资源,开发难度比较大,压力测试做充分。
3)被观察者尽量自己做主
被观察者的状态改变不一定要通知观察者,被观察者可以增加逻辑决定是否通知观察者,而不是在消息到达观察者时才判断是否要消费。
设计原则
1)找出程序中会变化的方面,然后将其和固定不变的方面相分离。
在观察者模式中,会改变的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。这就叫做提前规划。
2)针对接口编程,不针对实现编程。
主题和观察者都使用接口,观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。
3)多用组合,少用继承。
观察者模式利用”组合”将许多观察者组合进主题中。对象之间这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生的。
要点
描述
1)观察者模式定义了对象之间一对多的关系。
2)主题/可观察者用一个共同的接口来更新观察者。
3)观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者细节,只知道观察者实现了观察者接口。
4)使用此模式时,可从被观察者处推/push或拉/pull数据(然而,推的方式被认为更”正确”)。
5)有多个观察者时,不可以依赖特定的通知次序。
6)java有多种观察者模式的实例,包括了通用的java.util.Observable.
7)要注意java.util.Observable实现上所带来的一些问题。
8)如果有必要的话,可以实现自己的Observable.
9)swing大量使用观察者模式,许多GUI框架也是如此.
10)此模式也被应用在许多地方,例如:JavaBeans、RMI.
这和一对多的关系有何关联?
利用观察者模式,主题是具有状态的对象,并且可以控制这些状态。也就是说,有”一个”具有状态的主题。另一方面,观察者使用这些状态,虽然这些状态不属于他们。有许多的观察者,依赖主题来告诉他们状态何时改变了。这就产生一个关系:”一个”主题对”多个”观察者的关系。
期间的依赖是如何产生的?
因为主题是真正拥有数据的人,观察者是主题的依赖者,在数据变化时更新,这样比起让许多对象控制同一份数据来,可以得到更干净的OO设计。
松耦合
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。关于观察者的一切,主题只知道观察者实现了某个接口。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。主题和观察者并非紧耦合。
观察者模式为了交互对象之间的松耦合设计而努力。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
标题:设计模式(十四)行为模式(对象行为型模式)观察者模式-OBSERVER
作者:yazong
地址:https://blog.llyweb.com/articles/2020/07/21/1595345747938.html