1.概念
观察者模式属于对象行为型模式,指的是当多个对象之间存在一对多的依赖关系时,当一个对象的状态或行为发生改变时,所有依赖它的对象会自动被触发更新操作,该模式又称之为发布-订阅模式,提到发布-订阅,不由得想到消息队列,是的,从广义上理解,他们之间有一丝相似之处。
观察模式中有几种角色:
- 抽象观察目标(Subject):指被观察者对象观察的对象,观察目标应该持有所有观察者,自身状态发生改变后,应及时的将改变通知到观察者对象,观察者对象根据实际情况作出对应的改变行为
- 抽象观察者(Observer):观察者需向被观察者对象提前注册自己的身份,以便被观察发生改变后,能够将改变通知到自己
- 具体观察目标(ConcreteSubject):观察目标的具体实现
- 具体观察者(ConcreteObserver):观察者的具体实现
处理以上四种角色之外,还应该有对事件的抽象机制:
抽象事件(Event):对事件的抽象描述
具体事件(ConcreteEvent):对事件的具体描述
增加抽象层是为了更好的规范和扩展,其实从水平方向简单来理解就是三个角色:
- 观察者
- 被观察者,也可以理解为观察者的管理对象
- 事件
再精简一下就是一句话:当被观察者发生改变时,通过事件的方式向观察者发布通知,观察者根据具体事件作出对应的行为。
以两个例子来简单说明:
张三开设了一个流浪宠物收容所,宠物园里有猫和狗两种宠物,每天要给收容所里的他们喂食,食物有鱼和骨头,张三的收容所就是被观察对象,收容所里收容了猫和狗,猫和狗就是观察者,而事件就是喂食这件事。张三每天向所有宠物们发布骨头和鱼,当食物准备好后,猫会选择吃鱼,而狗则会选择骨头,如果此次没有发布骨头,那狗什么也不干,继续观察。这里假设猫只吃鱼,狗只吃骨头。
再比如,居委会微信群里发布了一则消息,家里60岁以上的老人可以免费领取足力健,穿上足力健可以一步登泰山,这个消息可以看做是一个事件。群里的所有业主都会收到这个事件,但是不同的人群会根据事件作出不同的行为,比如家里所有人年龄加起来也没有60岁,对于这样的业主自然这不会理会这个事件,换成其他的消息呢,同样,业主们会根据消息的内容判断是否影响到自身,进而决定要不要作出改变。
丢一个UML图:
图片来源:百度图片
2.示例
以第一个简单示例来说明,首先观察目标的抽象和具体实现
/**
* @description: 观察者管理接口 subject
* @version: 1.0
*/
public interface PetsKeeper {
void registerListener(Listener listener);
void removeListener(Listener listener);
void publishEvent(Event event);
}
/**
* @description: 观察目标具体实现subject impl
* @version: 1.0
*/
public class PetsKeeperImpl implements PetsKeeper {
/*
观察目标应持有所有依赖的观察者
*/
private final Vector<Listener> listeners = new Vector<>();
@Override
public void registerListener(Listener listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener listener) {
listeners.add(listener);
}
@Override
public void publishEvent(Event event) {
for (Iterator<Listener> it = listeners.iterator();it.hasNext();){
Listener next = it.next();
next.onEvent(event);
}
}
}
定义观察者和具体实现
public interface Listener {
void onEvent(Event event);
}
public class CatListener implements Listener{
@Override
public void onEvent(Event event) {
if (event instanceof FishEvent){
System.out.println(event.getNotice() + "=>猫吃鱼");
}
}
}
public class DogListener implements Listener{
@Override
public void onEvent(Event event) {
if (event instanceof BoneEvent){
System.out.println(event.getNotice() + "=>狗吃骨头");
}
}
}
定义事件
/**
* @description: 事件抽象层
* @version: 1.0
*/
public abstract class Event {
private String notice;
public String getNotice() {
return notice;
}
public void setNotice(String notice) {
this.notice = notice;
}
}
对事件分别做不同的实现
public class FishEvent extends Event{
public FishEvent(String notice) {
this.setNotice(notice);
}
}
public class BoneEvent extends Event{
public BoneEvent(String notice) {
this.setNotice(notice);
}
}
测试
/**
* @description: test
* @version: 1.0
*/
public class Test {
public static void main(String[] args) {
//创建观察者 分别是猫和狗
Listener dogListener = new DogListener();
Listener catListener = new CatListener();
//创建宠物管理员
PetsKeeper petsKeeper = new PetsKeeperImpl();
//将宠物注册到宠物园
petsKeeper.registerListener(dogListener);
petsKeeper.registerListener(catListener);
//喂食 =>发布事件
petsKeeper.publishEvent(new BoneEvent("啊哈哈啊,骨头来了"));
petsKeeper.publishEvent(new FishEvent("啊哈哈啊,鱼来了"));
}
}
input:
啊哈哈啊,骨头来了=>狗吃骨头
啊哈哈啊,鱼来了=>猫吃鱼
以上只是一个非常简单的示例,目的是为了说明观察者模式的原理和思想,实际业务中要根据实际的业务需求,适当的作出一些调整扩展,比如以上示例中是一个同步执行流程,被观察者发布事件后,如果观察者比较多,亦或执行流程比较复杂,观察目标需要一直等待所有观察者执行完毕才可以返回。故可以考虑将这这个流程处理为异步流程,具体做法可以在观察目标中维护一个事件的阻塞队列,开启异步线程。再比如,如果观察者处理发生异常呢?那可能还需要设计降级机制,或者反馈机制等。
需要注意的是,观察者和观察目标之间不能出现循环依赖,可能导致系统崩溃。
想象一下,猫狗吃法会触发喂食,喂食会触发猫狗吃饭,猫狗被撑死,人被累死,你这样子我们很难办啊,那就别办啦~
优点:观察者和被观察者是抽象耦合的,他们之间建立了一套触发机制
缺点:除了上边谈到问题,观察者只知道观察目标发生了变化,具体变化情况并不知道
3.总结
设计往往只是一个简单的思想描述,具体实现起来是多种多样的,而应用到业务中的实现,更应该充分考虑实际业务需求,对设计作出更好扩充优化,比如结合其他的设计模式,这一点是灵活的,好的设计应结合设计模式且脱离设计模式束缚,具体场景具体分析,切勿生搬硬套。