原文链接:java设计模式详细讲解-观察者模式
如果想要更加详细的 Java中的23种设计模式视频资源,请点击链接:Java中的23种设计模式视频资源下载
对象之间存在多对一的依赖院系,被依赖方向多个依赖放分发、发布事件时的一种程序设计解决方案,此时,被依赖方的对象称之为Subject,依赖放的对象称之为ObServ er,Subject向ObServer通知。
例如微信公众号的关注,关注者就是ObServer,被关注的公众号就是Subject。当关注者关账了一个公众号,那么公众号在推送文章时,就会想所有关注者推送这篇文章;反之,如果关注者取消关注,则公众号在推送文章时不再向他推送文章。类似的场景还有订牛奶等场景。
观察者模式中Subject对象一般包含如下事件:
1、添加事件(当有新的ObServer接入时调用)
2、移除事件(当有ObServer停止接入时调用)
3、通知/发布事件(当有新事件/消息发布时,通过此接口通知所有的ObServer)
而ObServer则提供如下事件/方法:
1、事件/消息接收(用于接收Subject推送的事件/消息)
Subject对象和ObServer对象的关系如下:
Java内置观察者和上述的观察者模式原理大致相同,
只是在java内置观察者模式中,被依赖对象称之为ObServable,且ObServable是一个类,而不是一个接口;开发者在使用的过程中,要继承这个类;并且由于ObServable已经实现了一部分事件,例如增加、移除、通知,我们在继承这个类之后,就无须考虑实现这些方法。
依赖的对象也称之为ObServer(注意,这个是一个接口,而不是一个类,之所以设计为接口,是因为在不同的项目实现的时候,在接收事件中要处理的逻辑可能不尽相同),
在ObServer观察者可以选择是等待ObServable直接推送消息内容,还是等待ObServable通知,然后自己主动向ObServable拉去消息,这是在Java内置观察者中的可以实现的。
Javan内置观察者添加ObServer后推送是先进后出,即先添加的会放在后面推送,而后进来的会先被调用通知。
由于观察者模式为自己实现,本案例将通过接口来实现
被观察者接口
package com.beBianMin.shareLogger.animal.ObserverMode.subject; import com.beBianMin.shareLogger.animal.ObserverMode.observer.Observer; /** * 被观察者接口 * @author 郭鹏飞 * */ public interface Subject { /** * 注册新的观察者 * @param observer * @return */ public void registerObServer(Observer observer); /** * 已拆除观察者 * @param observer */ public void removeObserver(Observer observer); /** * 通知观察者 */ public void notifyObservers(); }
该接口有三个方法:注册、移除、通知
被观察者实现类:
package com.beBianMin.shareLogger.animal.ObserverMode.subject; import java.util.ArrayList; import java.util.List; import com.beBianMin.shareLogger.animal.ObserverMode.observer.Observer; /** * 被观察者实现 * @author 郭鹏飞 * */ public class CurrentSubject implements Subject{ //参数1 private float temperatrue; //参数2 private float pressure; //参数3 private float humodity; //设置一个观察者的列表 private Listobservers; public CurrentSubject() { observers = new ArrayList (); } public float getTemperatrue() { return temperatrue; } public void setTemperatrue(float temperatrue) { this.temperatrue = temperatrue; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } public float getHumodity() { return humodity; } public void setHumodity(float humodity) { this.humodity = humodity; } public void setData(float temperatrue, float pressure, float humodity) { this.temperatrue = temperatrue; this.pressure = pressure; this.humodity = humodity; dataChage(); } public void dataChage() { notifyObservers(); } @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 notifyObservers() { for(Observer observer:observers) { observer.update(getTemperatrue(), getPressure(), getHumodity()); } } }
这个类看看似有点乱,其实上面三个参数
//参数1 private float temperatrue; //参数2 private float pressure; //参数3 private float humodity;
是模拟要通知观察者的数据,这个可以随意设置,可以按照你需要通知的数据设置自己的对象格式。
在这个类里面要注意的是要设置一个用于存储观察者的容器对象并驶离话它
//设置一个观察者的列表 private Listobservers; public CurrentSubject() { observers = new ArrayList (); }
在这个实现类中注册方法中,把新的观察者放入到这个容易中,在移除方法中把观察者从这个容器中移除掉,在通知方法中遍历所有的贯彻着,讲上面三个参数传递到观察者中。
并且我们现在假设在初始化上面所说的三个参数时,假设数据就是变化了,然后通过调用dataChage()方法来调用器notifyObservers方法来通知观察者。
观察者接口
package com.beBianMin.shareLogger.animal.ObserverMode.observer; /** * 观察者接口 * @author 郭鹏飞 * */ public interface Observer { /** * 观察者被通知的方法 * @param temperatrue * @param pressure * @param humodity */ public void update(float temperatrue,float pressure ,float humodity); }
该接口只有一个接口,用于接收被观察者发布的数据
观察者试下你类,本案例模拟两个观察者来实现推送:
第一个观察者
package com.beBianMin.shareLogger.animal.ObserverMode.observer; /** * 第一个观察者 * @author 郭鹏飞 * */ public class CurrentObserver1 implements Observer{ @Override public void update(float temperatrue, float pressure, float humodity) { System.out.println("第一个观察者被通知的结果,temperatrue:"+temperatrue); System.out.println("第一个观察者被通知的结果,pressure:"+pressure); System.out.println("第一个观察者被通知的结果,FloatArrayAssert:"+humodity); } }
第二个观察者
package com.beBianMin.shareLogger.animal.ObserverMode.observer; /** * 第一个观察者 * @author 郭鹏飞 * */ public class CurrentObserver2 implements Observer{ @Override public void update(float temperatrue, float pressure, float humodity) { System.out.println("第二个观察者被通知的结果,temperatrue:"+temperatrue); System.out.println("第二个观察者被通知的结果,pressure:"+pressure); System.out.println("第二个观察者被通知的结果,FloatArrayAssert:"+humodity); } }
这两个观察者在实现的方法中只是打印了以下接收到的通知数据。
测试代码如下:
package com.beBianMin.shareLogger.animal.ObserverMode; import com.beBianMin.shareLogger.animal.ObserverMode.observer.CurrentObserver1; import com.beBianMin.shareLogger.animal.ObserverMode.observer.CurrentObserver2; import com.beBianMin.shareLogger.animal.ObserverMode.subject.CurrentSubject; public class TestClass { public static void main(String[] args) { CurrentObserver1 currentObserver1 ; CurrentObserver2 currentObserver2; CurrentSubject currentSubject ; System.out.println("测试添加除观察者================="); currentObserver1 = new CurrentObserver1(); currentObserver2 = new CurrentObserver2(); currentSubject = new CurrentSubject(); currentSubject.registerObServer(currentObserver1); currentSubject.registerObServer(currentObserver2); currentSubject.setData(1, 2, 3); System.out.println("测试移除观察者================="); currentSubject.removeObserver(currentObserver2); currentSubject.setData(5, 6, 7); } }
运行代码结果如下:
测试添加除观察者================= 第一个观察者被通知的结果,temperatrue:1.0 第一个观察者被通知的结果,pressure:2.0 第一个观察者被通知的结果,FloatArrayAssert:3.0 第二个观察者被通知的结果,temperatrue:1.0 第二个观察者被通知的结果,pressure:2.0 第二个观察者被通知的结果,FloatArrayAssert:3.0 测试移除观察者================= 第一个观察者被通知的结果,temperatrue:5.0 第一个观察者被通知的结果,pressure:6.0 第一个观察者被通知的结果,FloatArrayAssert:7.0
发现第一次测试中,两个贯彻着都被通知到了,第二次测试中移除了第二个观察者,再次通知,只能通知到第一个观察者。并且发现一个现象:谁先被添加,谁先被通知(先进先出),这个现象在Java内置观察者中并不一致
在上面讲述Java内置观察者时咱们已经说过,Java内置了观察者(ObServable类)和被观察者(ObServer 接口),因此我们使用Java内置观察者实现观察者时就很简单。
由于Java内置观察者中被观察者向观察者通知时需要用一个对象来传递,因此我们定义一个作为传递数据的载体pojo:
package com.beBianMin.beBianMin.ObserverMode4Java.pojo; public class Data4Java { // 参数1 private float temperatrue; // 参数2 private float pressure; // 参数3 private float humodity; public Data4Java(float temperatrue, float pressure, float humodity) { this.temperatrue = temperatrue; this.pressure = pressure; this.humodity = humodity; } public float getTemperatrue() { return temperatrue; } public void setTemperatrue(float temperatrue) { this.temperatrue = temperatrue; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } public float getHumodity() { return humodity; } public void setHumodity(float humodity) { this.humodity = humodity; } }
定义被观察者
package com.beBianMin.beBianMin.ObserverMode4Java.ObSeervable; import java.util.Observable; import com.beBianMin.beBianMin.ObserverMode4Java.pojo.Data4Java; /** * 被观察者实现 * * @author 郭鹏飞 */ public class CurrentSubject extends Observable { // 参数1 private float temperatrue; // 参数2 private float pressure; // 参数3 private float humodity; public void setTemperatrue(float temperatrue) { this.temperatrue = temperatrue; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } public float getHumodity() { return humodity; } public void setHumodity(float humodity) { this.humodity = humodity; } public void setData(float temperatrue, float pressure, float humodity) { this.temperatrue = temperatrue; this.pressure = pressure; this.humodity = humodity; dataChage(); } public void dataChage() { // setChanged方法会设置一个变量为true,它的作用是: // java内置观察者在通知的时候,会判断这个变量是不是为true,如果为true,则会通知,否则不会通知,而改制为人为false // 在同之前先调用setChanget()方法,表示数据是有更新的,时代吗更具有灵活性 // 你可以在通知代码中设置一个是否需要通知观察者的一个条件, // 例如当上面三个参数范围超过某个范围才会需要通知, // 这是就需要调用this.setChange()方法,表明参数右边,可以通知 this.setChanged(); // 方式1、只通知观察者,然后观察者调用被观察者拉去通知消息 // this.notifyObservers(); // 方式2、直接将一个对象传递给观察者 this.notifyObservers(new Data4Java(getHumodity(), getPressure(), getPressure())); } }
注意上面的这个被观察者,由于上文咱们说了Java内置观察者(Observable ,为一个类)已经实现了添加、移除、通知观察者的方法,因此咱们就不想要重载这两个方法了,而只需要自己定义如何调用观察者逻辑即可,就是上面的setData方法
上面的这个观察者实现中要注意一下几点:
1、需要继承Observable类
2、不需要重载添加、移除、通知方法
3、通知观察者有两个方法/代码 notifyObservers()和notifyObservers(Object arg),这两个方法就是上面我说的你可以选择通知观察者数据有变化还是仅仅直接将要通知的内容传递进去
4、在调用3中的通知方法时,请首先调用setChanged();方法,这个很重要。原因在于,Java内置观察者在通知观察者之前,会通过一个内置的变量(true/false)来决定是否通知观察者,而这个变量默认为false(有兴趣的可以看下源码),如果你在调用3中的通知方法前不加此方法,Java内置观察则会将不会去通知观察者。这个方法的作用就是:让你的通知逻辑更加灵活(例如自己设置一个是否通知观察这的阈值)。
接下来实现第一个观察者:
package com.beBianMin.beBianMin.ObserverMode4Java.ObServer; import java.util.Observable; import java.util.Observer; import com.beBianMin.beBianMin.ObserverMode4Java.pojo.Data4Java; /** * 第一个观察者 * * @author 郭鹏飞 */ public class CurrentObserver1 implements Observer { @Override public void update(Observable o, Object arg) { // 观察者中将通知到的对象转换为实际对象 Data4Java data4Java = (Data4Java) arg; System.out.println("第一个观察者被通知的结果,temperatrue:" + data4Java.getTemperatrue()); System.out.println("第一个观察者被通知的结果,pressure:" + data4Java.getPressure()); System.out.println("第一个观察者被通知的结果,FloatArrayAssert:" + data4Java.getHumodity()); } }
第二个观察者:
package com.beBianMin.beBianMin.ObserverMode4Java.ObServer; import java.util.Observable; import java.util.Observer; import com.beBianMin.beBianMin.ObserverMode4Java.pojo.Data4Java; /** * 第二个观察者 * * @author 郭鹏飞 */ public class CurrentObserver2 implements Observer { @Override public void update(Observable o, Object arg) { // 观察者中将通知到的对象转换为实际对象 Data4Java data4Java = (Data4Java) arg; System.out.println("第二个观察者被通知的结果,temperatrue:" + data4Java.getTemperatrue()); System.out.println("第二个观察者被通知的结果,pressure:" + data4Java.getPressure()); System.out.println("第二个观察者被通知的结果,FloatArrayAssert:" + data4Java.getHumodity()); } }
上面咱们已经说了,观察者需要实现Observer接口(注意,是一个接口,之所以为接口,是因为他不知道你接收到数据后要干嘛,因此只能让你重载他的接收方法,然后你自己处理)
测试代码:
package com.beBianMin.beBianMin.ObserverMode4Java; import com.beBianMin.beBianMin.ObserverMode4Java.ObSeervable.CurrentSubject; import com.beBianMin.beBianMin.ObserverMode4Java.ObServer.CurrentObserver1; import com.beBianMin.beBianMin.ObserverMode4Java.ObServer.CurrentObserver2; public class TestClass { public static void main(String[] args) { CurrentObserver1 currentObserver1; CurrentObserver2 currentObserver2; CurrentSubject currentSubject; System.out.println("测试添加除观察者================="); currentObserver1 = new CurrentObserver1(); currentObserver2 = new CurrentObserver2(); currentSubject = new CurrentSubject(); currentSubject.addObserver(currentObserver1); currentSubject.addObserver(currentObserver2); currentSubject.setData(1, 2, 3); System.out.println("测试移除观察者================="); currentSubject.deleteObserver(currentObserver2); currentSubject.setData(5, 6, 7); } }
运行结果如下:
测试添加除观察者================= 第二个观察者被通知的结果,temperatrue:3.0 第二个观察者被通知的结果,pressure:2.0 第二个观察者被通知的结果,FloatArrayAssert:2.0 第一个观察者被通知的结果,temperatrue:3.0 第一个观察者被通知的结果,pressure:2.0 第一个观察者被通知的结果,FloatArrayAssert:2.0 测试移除观察者================= 第一个观察者被通知的结果,temperatrue:7.0 第一个观察者被通知的结果,pressure:6.0 第一个观察者被通知的结果,FloatArrayAssert:6.0
通过上面打印出的结果,我们可以发现如下现象:
1、通知结果为先进后出,即谁先被添加进去,谁会被放在后面通知
2、被观察者不能多重继承(由于它已经继承了一个类),这是Java内置观察者的一个缺点
1、观察者模式实现了松偶尔、高内聚,高度隔离
2、使用java内置观察者可以省去添加、移除、通知的方法,缺点就是不容易定制化开发,无法实现多重继承