设计模式 —– 观察者模式
个人博客,想要搭建个人博客的可以进来看看: http://www.ioqian.top/
观察者模式,在对象之间定义一对多的依赖,当一个对象状态改变时,依赖他的对象就会收到通知。典型的实现是java swing中的组件监听事件,当我们点击按钮时会调用我们注册的回调函数;还有Rxjava等等
背景
我们是一个天气台,有许多客户,当我们天气信息更新时,我们需要通知用户进行刷新…,我们要利用的是观察者模式
观察者模式主要由下面几部分组成:
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象
- WeatherData :具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知
- Observer:抽象观察者,是观察者者的抽象类,它是一个接口,里面有一个很重要的方法,当具体主题状态改变时,直接调用这个方法,这里实现了松耦合
- BoardCallOne :具体观察者,是实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
结合我们的背景和下面的代码,我们天气台就是一个具体主题,我们的客户就是具体观察者,我们这里抽象出了两个接口是为了实现松耦合
松耦合设计原则
当两个对象松耦合时,他们可以互相交互,但是不太清楚彼此之间的细节,我们这里让主题和观察者之间松耦合
我们怎么利用接口实现的松耦合哪?
- 关于观察者的一切,主题只知道观察者实现了Observer接口,主题不需要知道具体的观察者是谁,有什么细节,只要继承了Observer接口,有update()实现方法就可以
- 任何时候都可以增加新的观察者,因为主题仅仅依赖了Observer接口,我们可以随时增加或者删除观察者
- 有新的观察者时,主题的代码不要修改
抽象主题 Subject
public interface Subject {
//该方法把任意的观察者加入到观察者集合
public void registerObserver(Observer o);
//该方法把观察者从观察者从集合中除去,从此收不到状态改变
public void removeObserver(Observer o);
//当状态改变时通过观察者集合中的所以观察者
public void notifyAllObserver();
}
抽象观察者 Observer
public interface Observer {
public void update(float temp,float humidity);
}
具体主题 WeatherData
public class WeatherData implements Subject {
//观察者集合
private List<Observer> observers;
//模拟自身状态,当这些状态改变时通知所有观察者
private float temperature;
private float humidity;
public WeatherData(){
observers = new ArrayList<>();
}
//模拟状态改变通知所有观察者
public void stateChanged(float temp , float humidity){
this.temperature = temp;
this.humidity = humidity;
notifyAllObserver();
}
//模拟状态改变通知所有观察者
public void stateChanged(){
notifyAllObserver();
}
@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(i);
}
}
@Override
public void notifyAllObserver() {
for(Observer o : observers){
//这里很关键,这里调用的是抽象观察者的方法,不需要明白谁是观察者,只要继承了Observer的类就可以
o.update(temperature , humidity);
}
}
}
具体观察者 BoardCallOne
public interface DisplayDevice {
public void display();
}
//继承DisplayDevice接口和观察者模式没什么联系
public class BoardCallOne implements Observer ,DisplayDevice {
private static final String TAG = "BoardCallOne";
private float temp;
private float humidity;
//抽象主题主题
private Subject weatherData ;
public BoardCallOne(Subject weatherData) {
this.weatherData = weatherData;
//作为观察者,注册主题
weatherData.registerObserver(this);
}
//取消注册
public void removeRegister(){
weatherData.removeObserver(this);
}
//作为观察者必须实现的方法,所有当具体主题状态变化会通知我,我本身调用display()方法,实时刷新天气信息
@Override
public void update(float temp, float humidity) {
this.temp = temp;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println(TAG + " i get info from weather data {temp="+temp+",humidity="+humidity+"}");
}
}
测试main
public class Main {
public static void main(String[] args) throws InterruptedException {
//具体主题实现这
WeatherData subject = new WeatherData();
//具体观察者,在观察者的构造方法中注册了主题
BoardCallOne boardCallOne = new BoardCallOne(subject);
//在线程中一直让主题去通知观察者10次,每次休息1s
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ; i < 10 ;i++){
try {
subject.stateChanged(10.0f,18.0f);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
//主线程休眠5s后观察者取消注册,所以结果是会打印5次消息通知
Thread.sleep(5000);
boardCallOne.removeRegister();
//等待子线程结束
t.join();
}
}
结果验证了我们的猜测
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
Process finished with exit code 0
总结
1.我们这里的是push模式,push模式是主题状态改变去通知所有观察者;还有pull,观察者可以主动去获取主题的状态
2.观察者模式用到了那些设计原则
- 变化分离原则,在观察者模式中,会改变的是主题的状态,及观察者的类型和数目,我们可以改变改变依赖于主题的对象,不必改变主题
- 针对接口编程原则,主题和观察者都使用了接口。观察者利用主题的接口进行向主题注册(在代码具体观察者的构造函数中);主题利用接口通知观察者,实现了松偶尔
- 多用组合,少用继承 ,利用组合把众多观察者包含进主题,private List observers;