观察者模式介绍
观察者模式是我们项目中使用率非常高的一种设计模式,它最常用的地方就是GUI系统、订阅——发不系统,因为这个模式的一个重要的作用就是解耦,将被观察者和观察者解耦,使得他们之间的依赖性更小,甚至做到毫无依赖。
观察者模式的定义
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖与它的对象都会得到通知并被自动更新
观察者模式的UML类图
4.1 Subject 被观察者
定义被观察者必须实现的职责,他必须能够动态的添加,删除观察者。它一般是抽象或者实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
4.2 Observer 观察者
观察者接受到消息后,即立即进行update(更新)操作,对接收到的信息进行处理
4.3 ConcreteSubject 具体的被观察者
定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知
4.4 ConcreteObserver 具体的观察者
每个观察者 在接受到消息后的处理反应是不同,哥哥观察者有自己的处理逻辑。
观察者模式的简单实现
下面就以检测天气预报为例子,来实现一个简单的观察者模式,这里我们会举几个案例来一次讲解。
IWeather.java
/**
* 被观察者接口,为了简单期间,只具有两个功能,下雨、下雪和出太阳
*/
public interface IWeather {
//下雨
void rain();
//下雪
void snow();
//出太阳
void beSunny();
}
Weather .java
/**
* 具体的被观察者
*/
public class Weather implements IWeather{
//是否下雨
private boolean isRain;
//是否下雪
private boolean isSnow;
//是否出太阳
private boolean isSunny;
@Override
public void rain() {
System.out.println("老天开始下雨了");
isRain = true;
}
@Override
public void snow() {
System.out.println("老天开始下雪了");
isSnow = true;
}
@Override
public void beSunny() {
System.out.println("老天出太阳了");
isSunny = true;
}
/**
* 下面都是getter和setter方法
* @return
*/
public boolean isRain() {
return isRain;
}
public void setRain(boolean rain) {
isRain = rain;
}
public boolean isSnow() {
return isSnow;
}
public void setSnow(boolean snow) {
isSnow = snow;
}
public boolean isSunny() {
return isSunny;
}
public void setSunny(boolean sunny) {
isSunny = sunny;
}
}
IObservatory .java
/**
* 观察者接口,主要来观察天气的变化
*/
public interface IObservatory {
/**
* 观察到天气的变化,就通知人们
* @param result
*/
void upDate(String result);
}
ConcreteObservatory .java
/**
* 具体的观察者,天气气象站,如果观察的对象(天气)有变化,就要通知人们了
*/
public class ConcreteObservatory implements IObservatory{
@Override
public void upDate(String result) {
System.out.println("观察到天气的变化了,发送消息通知人们");
sendToPeople(result);
System.out.println("消息通知完毕!");
}
private void sendToPeople(String result){
System.out.println("人们接受到信息了," + result);
}
}
ObservatoryInstrument .java
/**
* 天气气象站的观察天气的仪器,一旦有情况就通知气象站
*/
public class ObservatoryInstrument extends Thread {
private Weather weather;
private ConcreteObservatory observatory;
public ObservatoryInstrument(Weather weather, ConcreteObservatory observatory) {
this.weather = weather;
this.observatory = observatory;
}
/**
* 无限监听天气情况
*/
@Override
public void run() {
System.out.println("监视系统已经开启");
while (true) {
try {
//没隔三秒检测一次
Thread.sleep(2000);
System.out.println("检测了一次");
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果发现在下雨,就通知天气气象站
if (weather.isRain()) {
//事实更新状态为下雨
observatory.upDate("下雨了");
//重置状态重新监控
weather.setRain(false);
} else if (weather.isSnow()) {
observatory.upDate("下雪了");
weather.setSnow(false);
} else if (weather.isSunny()) {
observatory.upDate("出太阳了");
weather.setSunny(false);
}
}
}
}
Test .java
/**
* 场景类
*/
public class Test {
public static void main(String[] args) {
//定义出被观察者天气对象
Weather weather = new Weather();
//创建具体的观察者天气气象站
ConcreteObservatory observatory = new ConcreteObservatory();
//创建监控对象
ObservatoryInstrument instrument = new ObservatoryInstrument(weather,observatory);
//开始监控
instrument.start();
//看看天气的变化情况
try {
Thread.sleep(2000);//模拟一段时间后
weather.rain();//下雨了
Thread.sleep(5000); //模拟一段时间后天气发生变化
weather.beSunny(); //出太阳了
Thread.sleep(5000);//一段时间后
weather.rain();//又下雨了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
打印结果如下:
监视系统已经开启
检测了一次
老天开始下雨了
检测了一次
观察到天气的变化了,发送消息通知人们
人们接受到信息了,下雨了
消息通知完毕!
检测了一次
老天出太阳了
检测了一次
观察到天气的变化了,发送消息通知人们
人们接受到信息了,出太阳了
消息通知完毕!
检测了一次
老天开始下雨了
检测了一次
观察到天气的变化了,发送消息通知人们
人们接受到信息了,下雨了
消息通知完毕!
检测了一次
检测了一次
检测了一次
....
....
....
这里无限打印检测一次
结果出来了,当天气下雨的时候,天气仪器已经监听到了气候变化,然后通过气象台通知了人们,当天气出太阳的时候也将结果告知了人们,但是这样就是我们的观察者模式了么?你会发现,虽然我们的结果是对的,但是你的CPU在一直不断的飙升,处于一直运行状态,因为在上面的这个程序中,我们使用了一个死循环来监听天气情况,要是用在项目当中,那可就惨了,不说别的,你的硬件设备就浪费了,差不多一台服务器就跑你这个死循环了。所以说,这个代码我们必须得改,这种代码是不可能放到项目中去的,并且这个完全不是面向对象的,而是面向过程的。那么我们该怎么改呢?可以这么想,既然天气一变化,仪器就能测到,并且能通知到气象站,那么我们完全可以把气象站聚集到天气这个类中呢?下面我们来开始看看修改后的代码。
Weather .java
/**
* 具体的被观察者
*/
public class Weather implements IWeather {
private ConcreteObservatory observatory = new ConcreteObservatory();
@Override
public void rain() {
System.out.println("老天开始下雨了");
observatory.upDate("下雨了");
}
@Override
public void snow() {
System.out.println("老天开始下雪了");
observatory.upDate("下雪了");
}
@Override
public void beSunny() {
System.out.println("老天出太阳了");
observatory.upDate("出太阳了");
}
}
我们将具体的被观察者修改成如上代码,然后将仪器对象移除,为了程序的简化,这里就不采用仪器来侧天气了,加入天气一有变化,气象站就能检测到天气情况,然后直接更新天气状态,测试代码修改如下。
Test.java
/**
* 场景类
*/
public class Test {
public static void main(String[] args) {
//创建天气对象
Weather weather = new Weather();
//天气下雨
weather.rain();
try {
Thread.sleep(5000);//模拟过一段时间后,开始下雪了。
//天气下雪
weather.snow();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果如下:
老天开始下雨了
观察到天气的变化了,发送消息通知人们
人们接受到信息了,下雨了
消息通知完毕!
老天开始下雪了
观察到天气的变化了,发送消息通知人们
人们接受到信息了,下雪了
消息通知完毕!
这样一来,我们的气象站类就不用定义在场景类中了,运行的结果也差不多。天气发生的变化也通知到位了。不过这样就符合观察者模式了么?其实不是的,在上面的代码中,我们知道,只要天气一有变化,即气象站就会将消息发送给所有的人,对于所有人这个概念我们也是没有具体话,所以下面我们把人也稍微具体一下。观察天气的应该不只一个气象台吧,应该有几个气象台,所以,我们将多添加几个气象台,也是三个不同省的气象台监测天气情况,然后分别将天气消息发送给对应的该省的所有人,下面我们先看看修改的代码,然后做具体的讲解。
Obserable .java
/**
* 被观察者接口
*/
public interface Obserable {
//添加一个观察者
void attachObserver(Observer observer);
//删除一个观察者
void deAttachObserver(Observer observer);
//发生状态的改变,就通知观察者
void notifyAllObservers(String result);
}
WeatherObserable .java
/**
* 具体的被观察者对象,天气
*/
public class WeatherObserable implements Obserable {
private List observers;
public WeatherObserable(){
observers = new ArrayList<>();
}
@Override
public void attachObserver(Observer observer) {
observers.add(observer);
}
@Override
public void deAttachObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyAllObservers(String result) {
for (Observer observer : observers) {
observer.update(result);
}
}
public void rain(){
System.out.println("老天要下雨了");
notifyAllObservers("下雨了");
}
public void snow(){
System.out.println("老天要下雪了");
notifyAllObservers("下雪了");
}
public void beSunny(){
System.out.println("老天出太阳了");
notifyAllObservers("出太阳了");
}
}
Observer.java
/**
* 观察者接口
*/
public interface Observer {
//发现天气有变化,就通知所有观察者
void update(String result);
}
HNObsertory.java
/**
* 具体的观察者对象,湖南省的气象台
*/
public class HNObsertory implements Observer {
@Override
public void update(String result) {
System.out.println("湖南省的气象台监听到了天气变化");
sentToShanDongAllPeople(result);
System.out.println("消息发送完毕");
}
private void sentToShanDongAllPeople(String result){
System.out.println("湖南省的所有人已经收到消息了:" + result);
}
}
SDObsertory.java
/**
* 具体的观察者对象,山东省的气象台
*/
public class SDObsertory implements Observer {
@Override
public void update(String result) {
System.out.println("山东省的气象台监听到了天气变化");
sentToShanDongAllPeople(result);
System.out.println("消息发送完毕");
}
private void sentToShanDongAllPeople(String result){
System.out.println("山东省的所有人已经收到消息了:" + result);
}
}
ZJObsertory.java
/**
* 具体的观察者对象,浙江省的气象台
*/
public class ZJObsertory implements Observer {
@Override
public void update(String result) {
System.out.println("浙江省的气象台监听到了天气变化");
sentToShanDongAllPeople(result);
System.out.println("消息发送完毕");
}
private void sentToShanDongAllPeople(String result){
System.out.println("浙江省的所有人已经收到消息了:" + result);
}
}
Test.java
/**
* 场景类
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
//三个气象台对象
Observer sdObserver = new SDObsertory();
Observer hnObserver = new HNObsertory();
Observer zjObserver = new ZJObsertory();
//被观察者天气对象
WeatherObserable obserable = new WeatherObserable();
//将需要监听天气的气象台观察者与被观察者天气相关联
obserable.attachObserver(sdObserver);
obserable.attachObserver(hnObserver);
obserable.attachObserver(zjObserver);
//下雪了
obserable.snow();
//模拟一段时间后的天气变化
System.out.println("*********一段时间过后***********");
Thread.sleep(5000);
//出太阳了
obserable.beSunny();
}
}
输出结果如下:
老天要下雪了
山东省的气象台监听到了天气变化
山东省的所有人已经收到消息了:要下雪了
消息发送完毕
湖南省的气象台监听到了天气变化
湖南省的所有人已经收到消息了:要下雪了
消息发送完毕
浙江省的气象台监听到了天气变化
浙江省的所有人已经收到消息了:要下雪了
消息发送完毕
*********一段时间过后***********
老天出太阳了
山东省的气象台监听到了天气变化
山东省的所有人已经收到消息了:要出太阳了
消息发送完毕
湖南省的气象台监听到了天气变化
湖南省的所有人已经收到消息了:要出太阳了
消息发送完毕
浙江省的气象台监听到了天气变化
浙江省的所有人已经收到消息了:要出太阳了
消息发送完毕
好了,上面的代码不仅结果是正确的,而且也符合我们的开闭原则,同时也实现了类间的解耦,如果还有哪个省份也要监听天气,直接与被观察者关联即可,只需要实现一下Observer观察者对象,然后在场景类中创建该实例,然后绑定关系。这就是我们的观察者模式。
6. 观察者模式的扩展实现
下面我们来对普通的观察者模式案来进行一下优化,从上面的代码,我们应该可以在其基础上进行一下抽取,从上面的WeatherObserable这个实现类中,我们可以抽象出一个父类,父类完全作为被观察者的职责,每一个被观察者只实现自己的逻辑方法就可以了,如此则非常符合单一原则了。刚好在Java中,就为我们提供了一个这样的父类,及java.util.Observable,有兴趣的大家可以自己去看一下源码,其实和我们的实现是差不多的,只是他里面多了一下安全机制,也就是使用了一个同步的方法,下面看看我们优化后的代码
IWeather.java
/**
* 被观察者接口,一个已经定义好的接口,所有被观察者统一实现该接口,如果以后需要监测其他天气可以在接口
* 里面直接添加,扩展性非常好
*/
public interface IWeather {
//下雨
void rain();
//下雪
void snow();
//出太阳
void beSunny();
}
WeatherObservable.java
/**
* 优化后的具体的被观察者,天气,只需要继承java为我们提供的Observable,然后定义好的被观察者接口即可
*/
public class WeatherObservable extends Observable implements IWeather {
@Override
public void rain() {
System.out.println("老天要下雨了");
//记住,这条语句一定要设置,不然,下面的notifyObserver方法调用就不会有任何作用了
super.setChanged();
super.notifyObservers("要下雨了");
}
@Override
public void snow() {
System.out.println("老天要下雪了");
super.setChanged();
super.notifyObservers("要下雪了");
}
@Override
public void beSunny() {
System.out.println("老天要出太阳了");
super.setChanged();
super.notifyObservers("要出太阳了");
}
}
ZJObsertory.java
/**
* 优化后具体的观察者对象,浙江省的气象台,直接实现java为我们提供的Observer接口即可
*/
public class ZJObsertory implements Observer {
/**
* 实现系统的update方法
* @param o 被观察对象,有时候我们需要获取被观察者的一些数据
* @param arg 天气结果
*/
@Override
public void update(Observable o, Object arg) {
System.out.println("浙江省的气象台监听到了天气变化");
sentToShanDongAllPeople((String)arg);
System.out.println("消息发送完毕");
}
private void sentToShanDongAllPeople(String result){
System.out.println("浙江省的所有人已经收到消息了:" + result);
}
}
HNObsertory.java
/**
* 具体的观察者对象,湖南省的气象台
*/
public class HNObsertory implements Observer {
/**
* 实现系统的update方法
* @param o 被观察对象,有时候我们需要获取被观察者的一些数据
* @param arg 天气结果
*/
@Override
public void update(Observable o, Object arg) {
System.out.println("湖南省的气象台监听到了天气变化");
sentToShanDongAllPeople((String)arg);
System.out.println("消息发送完毕");
}
private void sentToShanDongAllPeople(String result){
System.out.println("湖南省的所有人已经收到消息了:" + result);
}
}
SDObsertory.java
/**
* 具体的观察者对象,山东省的气象台
*/
public class SDObsertory implements Observer {
/**
* 实现系统的update方法
* @param o 被观察对象,有时候我们需要获取被观察者的一些数据
* @param arg 天气结果
*/
@Override
public void update(Observable o, Object arg) {
System.out.println("山东省的气象台监听到了天气变化");
sentToShanDongAllPeople((String)arg);
System.out.println("消息发送完毕");
}
private void sentToShanDongAllPeople(String result){
System.out.println("山东省的所有人已经收到消息了:" + result);
}
}
Test.java
/**
* 场景类
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
//三个气象台对象
Observer sdObserver = new SDObsertory();
Observer hnObserver = new HNObsertory();
Observer zjObserver = new ZJObsertory();
//被观察者天气对象
WeatherObservable obserable = new WeatherObservable();
//将需要监听天气的气象台观察者与被观察者天气相关联
obserable.addObserver(sdObserver);
obserable.addObserver(hnObserver);
obserable.addObserver(zjObserver);
//下雪了
obserable.snow();
//模拟一段时间后的天气变化
System.out.println("*********一段时间过后***********");
Thread.sleep(5000);
//出太阳了
obserable.beSunny();
}
}
输出结果如下:
老天要下雪了
浙江省的气象台监听到了天气变化
浙江省的所有人已经收到消息了:要下雪了
消息发送完毕
湖南省的气象台监听到了天气变化
湖南省的所有人已经收到消息了:要下雪了
消息发送完毕
山东省的气象台监听到了天气变化
山东省的所有人已经收到消息了:要下雪了
消息发送完毕
*********一段时间过后***********
老天要出太阳了
浙江省的气象台监听到了天气变化
浙江省的所有人已经收到消息了:要出太阳了
消息发送完毕
湖南省的气象台监听到了天气变化
湖南省的所有人已经收到消息了:要出太阳了
消息发送完毕
山东省的气象台监听到了天气变化
山东省的所有人已经收到消息了:要出太阳了
消息发送完毕
好了,优化的观察者模式也实现了,观察者模式大概就是这样,关于java为我们提供的Observable和Observer,一定要自己去实现下,然后看看Observable类中的源码,其中还有一个方法就是notifyObservers()不带参数的通知方法,大家可以看看这个具体有什么区别,有些小细节的问题,得自己去发现,才会有收获。
7. 观察者模式的优点
7.1 观察者和被观察者之间是抽象耦合
这样的设计,则不管是增加观察者还是被观察者都非常容易扩展,而且Java中也都实现了抽象层级的定义,在系统扩展方面也是得心应手。
7.2 建立一套触发机制
根据单一原则,每个类的职责是单一的。
8. 观察者模式的缺点
观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。多级触发时的效率更是让人担忧,希望在设计的时候注意考虑一下这方面的问题。