1、什么是观察者模式:
先看几个案例
(1)案例:
案例1:当前疫情严重,各个省区开学时间未定,但是各省区的开学时间要根据教育部的规定来制定,所以当教育部发布开学通知后,各省教育厅也会发布本省的开学计划。
案例2:一些公司的假期安排和国家法定节假日的安排不一致,在这种情况下,员工在公司没有发布放假通知的时候,一般不会轻易的安排假期出行计划,当看到公司的放假安排后,公司内员工就会根据放假通知来安排假期生活。
类似案例很多,有许多共性:
多个对象(observer)的行为依赖于一个对象(subject)的行为,也即observer与subject存在多对一的关系,当subject对象修改了某些属性或者执行某个方法,总之就是subject发生了变化,则多个object也要进行相应的变化。
(2)概述
观察者模式定义了对象之间的多对一依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。此处,发生改变的对象称之为观察目标(Subject),而被通知的对象称之为观察者(Observer)。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。观察者模式又称为发布-订阅模式。
(3)模式结构
观察者模式一般需要四个角色:
抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,比如可以定义为ArrayList类型,通过add和remove来增加或者删除观察者对象。
具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有(可以通过遍历ArrayList元素的方式逐个通知)登记过的观察者发出通知。
抽象观察者(Observer):为所有的具体观察者定义一个接口(当观察者发现subject发生变化时需要作出的响应),在得到主题通知时更新自己。
具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。
2、代码案例
(1)场景介绍
教育部公布了开学计划:剩余的假期的天数,开学后每周上课天数,然后安徽省和河南省教育厅看到教育部通知后,指定并发布本省的开学计划。
(2)代码
a、首先定义subject接口类Subject.java、至少包含注册、移除注册和通知所有观察者三个方法
package com.observe;
// subject接口,主题接口,里面定义了注册、删除和通知服务
public interface Subject {
// 注册观察者(增加)
public void registerObserver(Observer observer);
// 删除观察者
public void removeOberver(Observer observer);
// 当主题状态发生改变时,需要调用这个方法,以通知所有观察者
public void notifyObserver();
}
b、定义定义observer接口类Observer.java,至少包含更新方法(当发现subject发生变化时自己需要执行的操作)
package com.observe;
// 观察者接口,定义的服务应该是看到被观察者subject发生变化后的反馈行动
public interface Observer {
// 观察到subject变化后执行的操作
public void changeBySubject(int schoolStartsDaysLeft, int studyDays);
}
c、具体的subject类MinistryOfEducation.java,需要实现Subject接口,在实际业务中,那些主动变化且变化后引起其他对象变化的对象定义为此类,此处把教育部定义为具体的subject类,因为教育部状态发生变化会引起各个省份的做出相应的变化。
package com.observe;
import java.util.ArrayList;
import java.util.List;
/**
* @author wangql
* @date 2020/4/5 22:38
* @描述: MinistryOfEducation 是具体的被观察的对象,也即是多对一中的“一”所对应的对象
*/
public class MinistryOfEducation implements Subject{
// 因为是多对一关系,所以此处的observers定义为list类型,通过add和remove来动态注册和注销
private List<Observer> observers;
private int schoolStartsDaysLeft;
private int studyDays;
public MinistryOfEducation(){
observers = new ArrayList<Observer>();
}
// 覆盖接口中的通知方法
@Override
public void notifyObserver() {
for(int i = 0; i < observers.size();i++){
Observer observer = observers.get(i);
observer.changeBySubject(schoolStartsDaysLeft, studyDays);
}
}
// 覆盖接口中的注册方法
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
// 覆盖接口中的移除方法
@Override
public void removeOberver(Observer observer) {
int i = observers.indexOf(observer);
if(i >= 0){
observers.remove(i);
}
}
// subject中所谓的变化(比如属性数据发生变化或者执行了某种操作),变化发生时执行通知方法
public void change(int schoolStartsDaysLeft,int studyDays){
this.schoolStartsDaysLeft = schoolStartsDaysLeft;
this.studyDays = studyDays;
// 上面是变化,下面是变化后触发的通知方法,此方法将会把观察者中相应的反馈方法都触发
notifyObserver();
}
}
d、本案例打算模拟安徽省和河南省两个省份作为观察者,此处定义第一个观察者AnHui.java,要实现Observer.java接口
package com.observe;
/**
* @author wangql
* @date 2020/4/5 22:44
* @描述: 此类实现了Observer接口,也即是多对一中的“多”,是一个观察者
*/
public class AnHui implements Observer {
private int schoolStartsDaysLeft;
private int studyDays;
private MinistryOfEducation ministryOfEducation;
// 构造方法
public AnHui(MinistryOfEducation ministryOfEducation){
this.ministryOfEducation = ministryOfEducation;
ministryOfEducation.registerObserver(this); //注册观察者
}
// 接口Observer中的方法,检测到Subject变化后触发的服务
public void changeBySubject(int schoolStartsDaysLeft, int studyDays) {
this.schoolStartsDaysLeft = schoolStartsDaysLeft + 3;
this.studyDays = studyDays - 1;
System.out.println("AnHui's schoolStartsDaysLeft:"+this.schoolStartsDaysLeft
+"-----studyDays: "+this.studyDays);
}
}
e、此处定义第一个观察者HeNan.java,要实现Observer.java接口
package com.observe;
/**
* @author wangql
* @date 2020/4/5 23:41
* @描述
*/
public class HeNan implements Observer {
private int schoolStartsDaysLeft;
private int studyDays;
private MinistryOfEducation ministryOfEducation;
// 构造方法
public HeNan(MinistryOfEducation ministryOfEducation){
this.ministryOfEducation = ministryOfEducation;
ministryOfEducation.registerObserver(this); //注册观察者
}
// 接口Observer中的方法,检测到Subject变化后触发的服务
public void changeBySubject(int schoolStartsDaysLeft, int studyDays) {
this.schoolStartsDaysLeft = schoolStartsDaysLeft + 4;
this.studyDays = studyDays - 2;
System.out.println("HeNan's schoolStartsDaysLeft:"+this.schoolStartsDaysLeft
+"-----studyDays: "+this.studyDays);
}
}
f、主类:
package com.observe;
/**
* @author wangql
* @date 2020/4/5 22:50
* @描述
*/
public class MainTest {
public static void main(String[] args) {
// 创建一个Subject实例对象
MinistryOfEducation ministryOfEducation = new MinistryOfEducation();
// 创建Observer实例对象,调用了有参构造方法
AnHui anHui = new AnHui(ministryOfEducation);
HeNan heNan = new HeNan(ministryOfEducation);
// subject具体对象发生变化的方法,此方法触发通知方法,
//通知方法中遍历所有observer并执行每个observer中的changeBySubject方法,
//达到subject改变后所有的observer都跟着变化的目的
ministryOfEducation.change(21,5);
}
}
3、总结:
(1)两个接口:Subject.java和Observer.java,其中Subject中要包含注册、移除注册和通知所有观察者三个方法,Observer要包含一个根据Subject的变化而变化的方法
(2)一个具体Subject实现类和多个具体的Observer实现类,Subject实现类实现了Subject接口,Observer实现类实现了Observer.java接口
(2)subject对象如何与observer建立关系的:这个关系是双向建立的
subject——>observer,通过subject中的ArrayList的add和remove,实现了注册和移除注册的功能。
observer——>subject,在observer中加入了Subject接口实现类的属性,然后在构造方法中调用了实现类的add注册方法,也即注册发生在新的observer产生的时候,产生的时候进行注册。本案例中,AnHui类中包含MinistryOfEducation类型的属性 ministryOfEducation,在AnHui类的构造方法中,执行ministryOfEducation的注册方法,把新建的AnHui类对象加入到subject对象中。