设计模式之观察者模式
- 观察者模式定义
- 意图以及解决的问题
- 观察者模式关键及优缺点
- 观察者模式使用场景以及注意事项
- 观察者模式案例
观察者模式定义
- 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
- 可以把观察者模式想象成订报纸一样,出版者+订阅者 = 观察者模式;
意图以及解决的问题
- 解决的是当一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
- 使用场景: 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
观察者模式关键及优缺点
- 在抽象类(被观察者)里有一个 集合容器 存放观察者们。
- 优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
- 缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
观察者模式使用场景以及注意事项
- 使用场景一: 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 使用场景二: 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 使用场景三: 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 使用场景四: 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
- 注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
观察者模式案例
下面通过一个比较详细的案例使用观察者模式 :
- 首先写出普通的没有使用观察者模式的设计;
- 然后使用观察者模式设计;
- 再使用Java内置观察者类来设计;
这个题目要实现的功能是气象站的管理,给你一个WeatherData类,提供了获取温度,湿度,和气压的函数,要你设计类并添加一些公告板,可以显示相关的信息;如下是已经给定的WeatherData类:
public class WeatherData {
private double temperature;
private double humidity;
private double pressure;
public WeatherData() {
}
public double getTemperature() {
return temperature;
}
public double getHumidity() {
return humidity;
}
public double getPressure() {
return pressure;
}
public void setData(double temperature,double humidity,double pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
dataChange();
}
private void dataChange() {
}
}
一个不好的设计方案
类结构
修改的WeatherData类
package observer.Bad;
public class WeatherData {
private double temperature;
private double humidity;
private double pressure;
private CurrentConditionDisplay currentConditionDisplay;
public WeatherData(CurrentConditionDisplay currentConditionDisplay) {
this.currentConditionDisplay = new CurrentConditionDisplay();
}
public double getTemperature() {
return temperature;
}
public double getHumidity() {
return humidity;
}
public double getPressure() {
return pressure;
}
public void setData(double temperature,double humidity,double pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
dataChange();
}
private void dataChange() {
currentConditionDisplay.update(getTemperature(),getHumidity(),getPressure());
}
}
公告板类
package observer.Bad;
public class CurrentConditionDisplay {
private double temperature;
private double humidity;
private double pressure;
public void update(double temperature, double humidity, double pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
public void display() {
System.out.println("CurrentConditionDisplay : " +
"temperature=" + temperature +
", humidity=" + humidity +
", pressure=" + pressure );
}
}
测试类
package observer.Bad;
public class MyTest {
public static void main(String[] args) {
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay();
WeatherData weatherData = new WeatherData(currentConditionDisplay);
weatherData.setData(11,22,33);
}
}
显示结果:
CurrentConditionDisplay : temperature=11.0, humidity=22.0, pressure=33.0
这个方法的不好之处在于如果我再添加一个公告板,这样就需要去改动Weather类,这样设计的话增加了WeatherData和公告板的耦合度,不符合设计原则
使用自己写的观察者模式设计
看类图:
具体代码实现:
先看观察者和被观察者的接口:
package observer.GoodCustom.Interface;
/**
* 观察者 接口
*/
public interface Observer {
public void update(double temperature,double humidity,double pressure);
}
package observer.GoodCustom.Interface;
/**
* 被观察者接口
* 实现的功能是 : 添加观察者 ,移除观察者,通知观察者
*/
public interface Subject {
public void registerObservers(Observer o);//注册
public void removeObservers(Observer o); //移除
public void notifyObservers(); //通知
}
然后看WeatherData中具有观察者接口对象的结合,这里使用ArrayList集合来存放观察者;
package observer.GoodCustom.weather;
import observer.GoodCustom.Interface.Observer;
import observer.GoodCustom.Interface.Subject;
import java.util.ArrayList;
/**
* 被观察者,里面有观察者接口的集合数据结构
* 实现添加观察者方法(registerObservers)
* 移除观察者方法(removeObservers)
* 通知所有观察者的方法(notifyObservers)
*/
public class WeatherData implements Subject {
private double temperature;
private double humidity;
private double pressure;
private ArrayList<Observer>observers;
public WeatherData() {
observers = new ArrayList<Observer>();
}
public double getTemperature() {
return temperature;
}
public double getHumidity() {
return humidity;
}
public double getPressure() {
return pressure;
}
public void setData(double temperature,double humidity,double pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
dataChanged();//更新完信息就马上通知观察者
}
//数据改变之后就通知观察者(从气象站得到更新的观测值之后,通知观察者)
public void dataChanged(){
notifyObservers();
}
@Override
public void registerObservers(Observer o) {
observers.add(o);
}
@Override
public void removeObservers(Observer o) {
int index = observers.indexOf(o);
if(index >= 0){
observers.remove(o);
}
}
@Override
public void notifyObservers() {
for(int i = 0; i < observers.size(); i++){
Observer observer = (Observer)observers.get(i);
observer.update(getTemperature(),getHumidity(),getPressure());
}
}
}
然后再看两个观察者,也就是两个公告板:
package observer.GoodCustom.displays;
import observer.GoodCustom.Interface.Observer;
/**
* 观察者1
*/
public class CurrentConditionDisplay implements Observer{
private double temperature;
private double humidity;
private double pressure;
@Override
public void update(double temperature, double humidity, double pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
public void display() {
System.out.println("CurrentConditionDisplay : " +
"temperature=" + temperature +
", humidity=" + humidity +
", pressure=" + pressure );
}
}
package observer.GoodCustom.displays;
import observer.GoodCustom.Interface.Observer;
/**
* 观察者2
*/
public class TomorrowConditionDisplay implements Observer{
private double temperature;
private double humidity;
private double pressure;
@Override
public void update(double temperature, double humidity, double pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
public void display() {
System.out.println("TomorrowConditionDisplay : " +
"temperature=" + temperature*Math.random() +
", humidity=" + humidity*Math.random() +
", pressure=" + pressure*Math.random() );
}
}
测试类:
package observer.GoodCustom.test;
import observer.GoodCustom.displays.CurrentConditionDisplay;
import observer.GoodCustom.displays.TomorrowConditionDisplay;
import observer.GoodCustom.weather.WeatherData;
public class MyTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay current = new CurrentConditionDisplay();
TomorrowConditionDisplay tomorrow = new TomorrowConditionDisplay();
weatherData.registerObservers(current);
weatherData.registerObservers(tomorrow);
weatherData.setData(10,100,50);
System.out.println("----------移除Tomorrow公告板----------");
weatherData.removeObservers(tomorrow);
weatherData.setData(20,200,25);
}
}
运行结果
CurrentConditionDisplay : temperature=10.0, humidity=100.0, pressure=50.0
TomorrowConditionDisplay : temperature=3.1546512562643914, humidity=40.07629616522965, pressure=10.801468616917592
----------移除Tomorrow公告板----------
CurrentConditionDisplay : temperature=20.0, humidity=200.0, pressure=25.0
上面的方式很好的设计了,如果当再次添加观察者(公告板)的时候,可以不需要修改WeatherData类,达到更好的维护的效果。
使用Java内置的被观察者类和观察者接口实现
要注意一些特殊的地方
- 其中被观察者要继承的是Observable类(而不是接口);
- 在改变数据的时候,一定要调用内部的setChanged()方法(java内部是一个boolean型变量), 设置为了true(为了通知的灵活性)
- 被观察者可以使用两种方式通知观察者,包括自己”推送”数据和观察者自己”拉”数据;
具体代码实现:
WeatherData类(继承了Observable)
package observer.JavaInternal.weather;
import java.util.Observable;
/**
* 被观察者,里面有观察者接口的集合数据结构
* 实现添加观察者方法(registerObservers)
* 移除观察者方法(removeObservers)
* 通知所有观察者的方法(notifyObservers)
*/
public class WeatherData extends Observable {
private double temperature;
private double humidity;
private double pressure;
public double getTemperature() {
return temperature;
}
public double getHumidity() {
return humidity;
}
public double getPressure() {
return pressure;
}
public void setData(double temperature,double humidity,double pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
dataChanged();//更新完信息就马上通知观察者
}
//数据改变之后就通知观察者(从气象站得到更新的观测值之后,通知观察者)
public void dataChanged(){
this.setChanged(); //这个很重要,一定要设置这个,java底层有一个boolean值 changed = true;
notifyObservers(new Data(getTemperature(),getHumidity(),getHumidity())); //这个是 "推" 数据
// notifyObservers(); // 靠观察者自己 "拉" 数据
}
//数据传递类型
public static class Data{
private double temperature;
private double humidity;
private double pressure;
public Data(double temperature, double humidity, double pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
}
public double getTemperature() {
return temperature;
}
public double getHumidity() {
return humidity;
}
public double getPressure() {
return pressure;
}
}
}
两个公告版(都实现了Observer接口)
package observer.JavaInternal.displays;
import observer.JavaInternal.weather.WeatherData;
import java.util.Observable;
import java.util.Observer;
/**
* 观察者1
*/
public class CurrentConditionDisplay implements Observer{
private double temperature;
private double humidity;
private double pressure;
//这里表示的是直接接受 被观察者的数据("推" 过来的数据 ) --> 也可以自己获取("拉")数据
@Override
public void update(Observable o, Object data) { //注意这里还有被观察者的引用
this.temperature = ((WeatherData.Data)data).getTemperature(); //强制类型转换一下
this.humidity = ((WeatherData.Data)data).getHumidity();
this.pressure = ((WeatherData.Data)data).getPressure();
display();
}
public void display() {
System.out.println("CurrentConditionDisplay : " +
"temperature=" + temperature +
", humidity=" + humidity +
", pressure=" + pressure );
}
}
package observer.JavaInternal.displays;
import observer.JavaInternal.weather.WeatherData;
import java.util.Observable;
import java.util.Observer;
/**
* 观察者2
*/
public class TomorrowConditionDisplay implements Observer{
private double temperature;
private double humidity;
private double pressure;
@Override
public void update(Observable o, Object data) {
// this.temperature = ((WeatherData.Data)data).getTemperature();
// this.humidity = ((WeatherData.Data)data).getHumidity();
// this.pressure = ((WeatherData.Data)data).getPressure();
this.temperature = ((WeatherData)o).getTemperature();
this.humidity = ((WeatherData)o).getHumidity();
this.pressure = ((WeatherData)o).getPressure();
display();
}
public void display() {
System.out.println("TomorrowConditionDisplay : " +
"temperature=" + temperature*Math.random() +
", humidity=" + humidity*Math.random() +
", pressure=" + pressure*Math.random() );
}
}
测试类:
package observer.JavaInternal.test;
import observer.JavaInternal.displays.CurrentConditionDisplay;
import observer.JavaInternal.displays.TomorrowConditionDisplay;
import observer.JavaInternal.weather.WeatherData;
public class MyTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay current = new CurrentConditionDisplay();
TomorrowConditionDisplay tomorrow = new TomorrowConditionDisplay();
weatherData.addObserver(current);
weatherData.addObserver(tomorrow);
weatherData.setData(11,222,33);
System.out.println("-----------Remove Observer current------------");
weatherData.deleteObserver(current);
weatherData.setData(22,444,66);
}
}
输出
TomorrowConditionDisplay : temperature=3.6420702326805396, humidity=67.55176779271943, pressure=3.0205430841923926
CurrentConditionDisplay : temperature=11.0, humidity=222.0, pressure=222.0
-----------Remove Observer current------------
TomorrowConditionDisplay : temperature=15.452795901821323, humidity=366.9728803530541, pressure=59.9279544255332