一. 定义
许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或多个其他对象的行为也发生改变,例如敌军为数据集合(被观察者),军官为视图(观察者),中间的通知者就是侦察兵,侦察兵在前线侦查敌情的变动,一有风吹草动就会通知军官,军官采取相应的方案。这里军官为观察者,侦察兵为通知者(不同角度可能角色会转换)MVC模式中的模型与视图的关系,事件模型中的事件源与事件处理者,这种用观察者模式较方便;
观察者(Observer)模式:
1) 对象之间一种多对一的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
2) 又叫发布-订阅模式(Publish-Subscribe)。源-收听者(Source/Listener)模式, 模型-视图模式。
3) 它是对象的行为型模式
意图(需求):对象之间必然会存在依赖关系或者依赖关系会处于变动之中,如何解决依赖关系使他们之间的耦合性达到最小。
二. 特点
1. 优点
1> 降低了主题目标与观察者之间的耦合关系,让双方都依赖于抽象,从而使各自变换都不会影响另一边,符合依赖倒置原则;
2> 易于扩展,对同一主题新增观察者时无需修改原有代码;
2. 缺点
1> 目标与观察者之间依赖关系并未完全解除,抽象主题依然依赖抽象观察者(抽象观察者对象要传入到抽象被观察者中);
2> 当观察者对象很多时,可能会引起多余的数据通知,影响程序执行效率;
三. 应用场景
当系统一方行为依赖另一方行为的变动时,可使用观察者模式联动双方,使一方的变动可通知到感兴趣的另一方对象对此做出响应;
1) 跨系统的消息交换场景,如消息队列,事件总线的处理机制。
2) 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
四. 模式的结构
对象之间一种多对一的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,以集合方式管理;
注意具体目标对象 和 具体观察者对象之间不能直接调用,否则将使两者间紧密耦合起来,这违反面向对象的设计原则;
1. 模式结构图
2. 观察者模式角色分析
1>抽象被观察者(Subject/Observable)
又称抽象主题:它提供一个用于保存观察者对象引用的集合 和 增加、删除观察者对象的方法 及 通知所有观察者的抽象方法;
2>具体被观察者(ConcreteSubject/ConcreteObservable)
又称具体主题,保存对具体观察者对象有用的 内部状态,当具体主题(被观察者)内部状态改变时,通知所有注册过的观察者对象;
3>抽象观察者(Observer)
为所有的具体观察者定义一个更新接口,在得到主题(被观察者)通知时更新自己, 抽象观察者可用一个抽象类或一个接口来实现;
4>具体观察者(ConcreteObserver)
具体观察者实现抽象观察者所需要的更新接口update()方法,以便在得到被观察者状态更新通知时,更新自身状态;
五. 模式的实现
需求: 观察者模式场景: 邮递商品-->物流系统-->快递员--->收件人
1. 实例结构图
2. 相关代码实现
/**
* 1.抽象被观察者--物流系统
*/
abstract class Observable {
//抽象被观察者要求子类保持一个以所有观察者对象为元素的列表集合
protected val receiverList: MutableList<Observer> //收件人列表
init {
receiverList = ArrayList()
}
//通知订阅者更新消息, 通知收件人,物流状态更新
abstract fun notifyObserver(msg: String)
//添加一个观察者(收件人)
fun attach(observer: Observer) { //添加收件人
if (!receiverList.contains(observer)) { //去重
receiverList.add(observer)
}
}
// 删除一个观察者
fun detach(observer: Observer) { //移除收件人
receiverList.remove(observer)
}
//将观察者列表清空
fun clearObserver() {
receiverList.clear()
}
/**
* @return 返回被观察对象(即此对象)的观察者总数
*/
@Synchronized
fun countObservers(): Int {
return receiverList.size
}
}
/**
* 2.具体被观察者 --快递员
*/
open class Postman : Observable() {
//记录被观察者对象是否发生变化
private var changed = false
/**
* 将被观察者已变化的状态设置为true
*/
@Synchronized
fun setChanged() {
changed = true
}
/**
* 将已变化的状态重置为false
*/
@Synchronized
fun clearChanged() {
changed = false
}
/**
* 获取被观察对象是否已变化状态
*/
@Synchronized
fun hasChanged(): Boolean {
return changed
}
override fun notifyObserver(msg: String) { //逐一通知收件人
//临时存放当前观察者的状态
var arrLocal: List<Observer>
synchronized(this) {
if (!changed) return
arrLocal = receiverList
clearChanged()
}
for (observer in arrLocal) {
observer.update(msg)
}
}
}
/**
* 3.定义抽象观察者 观察者的联系方式:如手机通知
*/
interface Observer {
//监听到被观察者状态改变时的更新方法
fun update(msg: String?)
}
/**
* 4.具体的观察者1--男收件人
*/
class Boy(private val nickName: String) : Observer{
override fun update(msg: String?) {
if (msg is String) { //男收件人的具体反映
println("$nickName, 收到了消息: $msg , 一路小跑的去取快递了")
}
}
}
/**
* 4.具体的观察者2--女收件人
*/
class Girl(private val nickName: String) : Observer{
override fun update(msg: String?) {
if (msg is String) { //女收件人的具体反映
println("$nickName, 收到了消息:$msg ,让男朋友去取快递了")
}
}
}
//观察者模式场景:邮递商品-->物流系统-->快递员--->收件人
object ObserverClient {
@JvmStatic
fun main(args: Array<String>) {
//创建具体被观察者
val postman = Postman()
//创建具体观察者
val boy = Boy("北辰")
val girl = Girl("娜美")
//订阅具体被观察者
postman.attach(boy)
postman.attach(girl)
val count: Int = postman.countObservers()
postman.setChanged()
println("changed1: ${postman.hasChanged()}")
println("count: $count")
//更新被观察者状态
postman.notifyObserver("快递到了,请下楼领取")
println("changed2: ${postman.hasChanged()}")
}
}
程序运行结果
changed1: true
count==>2
北辰, 收到了消息: 快递到了,请下楼领取 , 后一路小跑的去取快递了
娜美, 收到了消息:快递到了,请下楼领取 ,让男朋友去取快递了
changed2: false
六.JDK中的定义
//抽象观察者
public interface Observer {
//只定义了一个update方法
void update(Observable o, Object arg);
}
//抽象被观察者
public class Observable {
private boolean changed = false;//定义改变状态,默认为false
private final ArrayList<Observer> observers;//定义一个观察者list
public Observable() {//构造函数,初始化一个观察者list来保存观察者
observers = new ArrayList<>();
}
//添加观察者,带同步字段的,所以是线程安全的
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!observers.contains(o)) {
observers.add(o);
}
}
//删除观察者
public synchronized void deleteObserver(Observer o) {
observers.remove(o);
}
//通知所以观察者,无参数
public void notifyObservers() {
notifyObservers(null);
}
//通知所有观察者,带参数
public void notifyObservers(Object arg) {
Observer[] arrLocal;
//加synchronized字段,保证多线程下操作没有问题
synchronized (this) {
if (!hasChanged())//这里做了是否发生改变的判断,是为了防止出现无意义的更新
return;
//ArrayList转换成一个临时的数组,这样就防止了通知,添加,移除同时发生可能导致的异常
arrLocal = observers.toArray(new Observer[observers.size()]);/
clearChanged();///清除改变状态,设置为false
}
//遍历逐一通知
for (int i = arrLocal.length-1; i>=0; i--)
arrLocal[i].update(this, arg);
}
//清除所有观察者
public synchronized void deleteObservers() {
observers.clear();
}
//设置被观察者为改变状态,设置为true
protected synchronized void setChanged() {
changed = true;
}
//清除改变状态,设置为false
protected synchronized void clearChanged() {
changed = false;
}
//返回当前的改变状态
public synchronized boolean hasChanged() {
return changed;
}
//观察者数量
public synchronized int countObservers() {
return observers.size();
}
}
七.EVentBus详解(基于observer)
1.EventBus使用了发布者/订阅者模式。
//EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程。
Event event--> Subscriber (onEvent())
Publisher ---------->EventBus-->
post() event--> Subscriber (onEvent())
2.EventBus总共支持5种线程模式:
EventBus支持订阅者方法在不同于发布事件所在线程的线程中被调用,你可以使用线程模式来指定调用订阅者方法的线程。
1>ThreadMode.POSTING
特点: 订阅者方法将在发布事件所在的线程中被调用。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。
场景: 这种线程模式意味着最少的性能开销,因为它避免了线程的切换。故对不要求是主线程且耗时很短的简单任务推荐使用该模式。
注意: 使用该模式的订阅者方法应该快速返回,以避免阻塞发布事件的线程,这可能是主线程。
2>ThreadMode.MAIN
特点:订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。
场景:如果发布事件的线程是主线程,那么该模式的订阅者方法将被直接调用。
注意:使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
3>ThreadMode.MAIN_ORDERED (有序)
特点: 订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。
场景: 事件将先进入队列然后才发送给订阅者,所以发布事件的调用将立即返回。这使得事件的处理保持严格的串行顺序。
注意: 使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
4>ThreadMode.BACKGROUND
特点: 订阅者方法将在后台线程中被调用,若发布事件的线程不是主线程,则该方法直接在该线程被调用,否则将使用一个单独的后台线程;
场景: 该线程将按顺序发送所有的事件。使用该模式的订阅者方法应该快速返回,以避免阻塞后台线程。
5>ThreadMode.ASYNC
特点: 订阅者方法将在一个单独的线程中被调用。因此,发布事件的调用将立即返回。
场景: 若订阅者方法的执行需要一些时间,如网络访问,就应该使用该模式,避免触发大量的长时间运行的订阅者方法,以限制并发线程数。
3.粘性事件
如果先发布了事件,然后有订阅者订阅了该事件,那么除非再次发布该事件,否则订阅者将永远接收不到该事件。
此时可使用粘性事件,发布一个粘性事件后,EventBus将在内存中缓存该粘性事件,当有订阅者订阅该粘性事件,订阅者将接收到该事件。
使用场景:我们要把一个Event发送到一个还没有初始化的Activity/Fragment,即尚未订阅事件。
那么如果只是简单的post一个事件,那么是无法收到的,这时候,你需要用到粘性事件,它可以帮你解决这类问题.
示例代码:
//1>.发布粘性事件:
EventBus.getDefault().postSticky(new MessageEvent("Hello EventBus!"));
//2>.接收event粘性事件(还有注册和注销EventBus)
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onMessageEvent(MessageEvent event) {
Log.i(TAG, "message is " + event.getMessage());
mTvMessage.setText(event.getMessage());// 处理业务逻辑
// 移除粘性事件
EventBus.getDefault().removeStickyEvent(event);
}
4.事件优先级
1>EventBus订阅者方法的事件传递优先级为0(默认)。数值越大,优先级越高。在相同线程模式下(前提),更高优先级的订阅者方法将优先接收事件。
@Subscribe(priority = 1) public void onMessageEvent(MessageEvent event) { ... }
2>取消事件传递:可以在 高优先级 的订阅者方法接收到事件之后取消事件的传递,此时低优先级的订阅者方法将不会接收到该事件。
注意事项:订阅者方法只有在线程模式为ThreadMode.POSTING时,才可以取消一个事件的传递。
@Subscribe(threadMode = ThreadMode.POSTING, priority = 1)
public void onMessageEvent(MessageEvent event) {
...
// 取消事件传递 EventBus.getDefault().cancelEventDelivery(event);
}