设计模式之观察者模式
观察者模式又叫做发布-订阅模式,属于行为型模式;观察者模式通过定义一种一对多得依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
观察者模式的UML类图如下:
如上图所示,观察者模式主要涉及到抽象主题角色、具体主题角色、抽象观察者角色、具体观察者角色等四种角色:
- 抽象主题(Subject)角色:主题角色把所有对观察者对象的引用保存在一个集合(比如 Vector对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做抽象被观察者( Observable)角色,一般用一个抽象类或者一个接口实现。
- 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫作具体被观察者角色(Concrete Observable)。具体主题角色通常用一个具体子类实现;具体主题角色负责实现对观察者引用的聚集的管理方法。
- 抽象观察者((Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者角色一般用一个抽象类或者一个接口实现。
- 具体观察者( ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
天气预报例子
现在看天气预报已经成为我们生活中不可缺少的一个行为了。现在可以通过很多渠道查看到天气情况,如新浪天气,百度天气,微信公众号天气等,这些查看天气的都是从中国气象局公布的天气情况数据来的。在这以这个为例子讲解观察者模式。
例子的UML类图如下:
抽象观察者:
package com.charon.observer;
/**
* @className: Observer
* @description: 抽象观察者
* @author: charon
* @create: 2022-03-30 23:15
*/
public interface Observer {
/**
* 更新天气情况
* @param temperature 气温
* @param pressure 气压
* @param humidity 湿度
*/
void update(float temperature, float pressure, float humidity);
}
具体观察者:
package com.charon.observer;
/**
* @className: CurrentConditions
* @description:
* @author: charon
* @create: 2022-03-30 23:17
*/
public class CurrentConditions implements Observer {
/**
* 气温,气压,湿度
*/
private float temperature, pressure, humidity;
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 模拟展示
display();
}
private void display() {
System.out.println("当前的气温是:" + temperature);
System.out.println("当前的气压是:" + pressure);
System.out.println("当前的湿度是:" + humidity);
}
}
package com.charon.observer;
/**
* @className: Baidu
* @description:
* @author: charon
* @create: 2022-03-30 23:20
*/
public class Baidu implements Observer{
/**
* 气温,气压,湿度
*/
private float temperature, pressure, humidity;
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 模拟展示
display();
}
private void display() {
System.out.println("百度天气查询得知,当前的气温是:" + temperature);
System.out.println("百度天气查询得知,当前的气压是:" + pressure);
System.out.println("百度天气查询得知,当前的湿度是:" + humidity);
}
}
抽象主题角色:
package com.charon.observer;
/**
* @className: Subject
* @description:
* @author: charon
* @create: 2022-03-30 23:24
*/
public interface Subject {
/**
* 注册一个观察者
* @param observer
*/
void registerObserver(Observer observer);
/**
* 删除一个观察者
* @param observer
*/
void removeObserver(Observer observer);
/**
* 通知观察者
*/
void notifyObserver();
}
具体观察者角色:
package com.charon.observer;
import java.util.ArrayList;
import java.util.List;
/**
* @className: WeatherData
* @description:
* @author: charon
* @create: 2022-03-30 23:27
*/
public class WeatherData implements Subject{
private List<Observer> observers;
/**
* 气温,气压,湿度
*/
private float temperature, pressure, humidity;
public WeatherData() {
this.observers = new ArrayList<>();
}
public void setWeatherData(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 模拟展示
notifyObserver();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if(observers.contains(observer)){
observers.remove(observer);
}
}
@Override
public void notifyObserver() {
observers.forEach(item -> {
item.update(this.temperature,this.pressure,this.humidity);
});
}
}
client:
package com.charon.observer;
/**
* @className: Client
* @description: http://c.biancheng.net/view/1390.html
* @author: charon
* @create: 2022-03-30 22:56
*/
public class Client {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
// 注册到具体观察者上
weatherData.registerObserver(new CurrentConditions());
weatherData.registerObserver(new Baidu());
weatherData.setWeatherData(10f,100f,30f);
}
}
打印:
当前的气温是:10.0
当前的气压是:100.0
当前的湿度是:30.0
百度天气查询得知,当前的气温是:10.0
百度天气查询得知,当前的气压是:100.0
百度天气查询得知,当前的湿度是:30.0
如上图的代码所示,观察者模式的例子就完成了,如果现在需要添加一个新浪天气的厂商,那么之需要让新浪天气实现Observer接口,然后注册到weatherData上就行了。
package com.charon.observer;
/**
* @className: sina
* @description:
* @author: charon
* @create: 2022-03-30 23:20
*/
public class sina implements Observer{
/**
* 气温,气压,湿度
*/
private float temperature, pressure, humidity;
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 模拟展示
display();
}
private void display() {
System.out.println("新浪天气查询得知,当前的气温是:" + temperature);
System.out.println("新浪天气查询得知,当前的气压是:" + pressure);
System.out.println("新浪天气查询得知,当前的湿度是:" + humidity);
}
}
观察者模式的主要优点如下:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。被观察者角色所知道的只是一个具体观察者,每一个具体观察者都符合一个抽象观察者的接口,被观察者并不认识任何一个具体观察者,他只知道他们都有一个共同的接口,由于被观察者和观察者没有紧密地耦合在一起,因此他们可以属于不同地抽象化层次。
- 符合依赖倒置原则及开闭原则。
观察者模式的主要缺点如下:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
模式的应用场景
在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。
观察者模式适合以下几种情形:
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
- 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
本文版权归Charon和博客园共有,原创文章,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。