您现在的位置是:首页 >技术杂谈 >设计模式之【观察者模式】,MQ的单机实现雏形网站首页技术杂谈
设计模式之【观察者模式】,MQ的单机实现雏形
文章目录
一、什么是观察者模式
观察者模式(Observer Pattern),又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。定义一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听,使得每当主题对象状态变化时,所有依赖于它的对象都会得到通知并被自动更新。属于行为型模式。
观察者模式的核心是将观察者与被观察者解耦,以类似于消息/广播发送的机制联动两者,使被观察者的变动能通知到感兴趣的观察者们,从而做出相应的响应。
1、观察者模式应用场景
在软件系统中,当系统一方行为依赖于另一方行为的变动时,可以使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。观察者模式适用于以下几种应用场景:
- 当一个抽象模型包含两个方面的内容,其中一个方面依赖于另一个方面;
- 其他一个或多个对象的变化依赖于另一个对象的变化;
- 实现类似广播机制的功能,无需知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播;
- 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
2、观察者模式的四大角色
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
3、观察者模式优缺点
优点:
- 观察者和被观察者是松耦合(抽象耦合)的,符合依赖倒置原则;
- 分离了表示层(观察者)和数据逻辑层(被观察者),并建立了一套触发机制,使得数据的变化可以响应到多个表示层上;
- 实现了一对多的通讯机制,支持事件注册机制,支持兴趣分发机制,当被观察者触发事件时,只有感兴趣的观察者可以接收到通知。
缺点:
- 如果观察者数量过多,则事件通知会耗时较长;
- 事件通知呈线性关系,如果其中一个观察者处理事件卡壳,会影响后续的观察者接收该事件;
- 如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,导致系统崩溃。
4、观察者模式和中介模式的区别
观察者模式有多种实现方式。虽然经典的实现方式没法彻底解耦观察者和被观察者,观察者需要注册到被观察者中,被观察者状态更新需要调用观察者的 update() 方法。但是,在跨进程的实现方式中,我们可以利用消息队列实现彻底解耦,观察者和被观察者都只需要跟消息队列交互,观察者完全不知道被观察者的存在,被观察者也完全不知道观察者的存在。
中介模式也是为了解耦对象之间的交互,所有的参与者都只与中介进行交互。而观察者模式中的消息队列,就有点类似中介模式中的“中介”,观察者模式的中观察者和被观察者,就有点类似中介模式中的“参与者”。
在观察者模式中,尽管一个参与者既可以是观察者,同时也可以是被观察者,但是,大部分情况下,交互关系往往都是单向的,一个参与者要么是观察者,要么是被观察者,不会兼具两种身份。也就是说,在观察者模式的应用场景中,参与者之间的交互关系比较有条理。
而中介模式正好相反。只有当参与者之间的交互关系错综复杂,维护成本很高的时候,我们才考虑使用中介模式。毕竟,中介模式的应用会带来一定的副作用,它有可能会产生大而复杂的上帝类。除此之外,如果一个参与者状态的改变,其他参与者执行的操作有一定先后顺序的要求,这个时候,中介模式就可以利用中介类,通过先后调用不同参与者的方法,来实现顺序的控制,而观察者模式是无法实现这样的顺序要求的。
二、实例
1、观察者模式的一般写法
//抽象观察者
public interface IObserver<E> {
void update(E event);
}
//具体观察者
public class ConcreteObserver<E> implements IObserver<E> {
public void update(E event) {
System.out.println("receive event: " + event);
}
}
//抽象主题者
public interface ISubject<E> {
boolean attach(IObserver<E> observer);
boolean detach(IObserver<E> observer);
void notify(E event);
}
//具体主题者
public class ConcreteSubject<E> implements ISubject<E> {
private List<IObserver<E>> observers = new ArrayList<IObserver<E>>();
public boolean attach(IObserver<E> observer) {
return !this.observers.contains(observer) && this.observers.add(observer);
}
public boolean detach(IObserver<E> observer) {
return this.observers.remove(observer);
}
public void notify(E event) {
for (IObserver<E> observer : this.observers) {
observer.update(event);
}
}
}
public class Test {
public static void main(String[] args) {
// 被观察者
ISubject<String> observable = new ConcreteSubject<String>();
// 观察者
IObserver<String> observer = new ConcreteObserver<String>();
// 注册
observable.attach(observer);
// 通知
observable.notify("hello");
}
}
2、微信公众号案例
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。
类图如下:
// 定义抽象观察者类,里面定义一个更新的方法
public interface Observer {
void update(String message);
}
// 定义具体观察者类,微信用户是观察者,里面实现了更新的方法
public class WeixinUser implements Observer {
// 微信用户名
private String name;
public WeixinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "-" + message);
}
}
// 定义抽象主题类,提供了attach、detach、notify三个方法
public interface Subject {
//增加订阅者
public void attach(Observer observer);
//删除订阅者
public void detach(Observer observer);
//通知订阅者更新消息
public void notify(String message);
}
// 微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
public class SubscriptionSubject implements Subject {
//储存订阅公众号的微信用户
private List<Observer> weixinUserlist = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
weixinUserlist.add(observer);
}
@Override
public void detach(Observer observer) {
weixinUserlist.remove(observer);
}
@Override
public void notify(String message) {
for (Observer observer : weixinUserlist) {
observer.update(message);
}
}
}
// 客户端程序
public class Client {
public static void main(String[] args) {
SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
//创建微信用户
WeixinUser user1=new WeixinUser("孙悟空");
WeixinUser user2=new WeixinUser("猪悟能");
WeixinUser user3=new WeixinUser("沙悟净");
//订阅公众号
mSubscriptionSubject.attach(user1);
mSubscriptionSubject.attach(user2);
mSubscriptionSubject.attach(user3);
//公众号更新发出消息给订阅的微信用户
mSubscriptionSubject.notify("秃了也弱了的专栏更新了");
}
}
3、鼠标响应事件API案例
做过安卓或者是桌面程序的小伙伴会对这很熟悉,鼠标点击事件也都是通过观察者模式来实现的,我们自己用代码来实现一下。
/**
* 观察者抽象
*/
public interface EventListener {
}
import java.lang.reflect.Method;
// 事件
public class Event {
//事件源,动作是由谁发出的
private Object source;
//事件触发,要通知谁(观察者)
private EventListener target;
//观察者给的回应
private Method callback;
//事件的名称
private String trigger;
//事件的触发事件
private long time;
public Event(EventListener target, Method callback) {
this.target = target;
this.callback = callback;
}
public Object getSource() {
return source;
}
public Event setSource(Object source) {
this.source = source;
return this;
}
public String getTrigger() {
return trigger;
}
public Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
}
public long getTime() {
return time;
}
public Event setTime(long time) {
this.time = time;
return this;
}
public Method getCallback() {
return callback;
}
public EventListener getTarget() {
return target;
}
@Override
public String toString() {
return "Event{" +
"source=" + source +
", target=" + target +
", callback=" + callback +
", trigger='" + trigger + ''' +
", time=" + time +
'}';
}
}
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* 被观察者的抽象
*/
public class EventContext {
protected Map<String,Event> events = new HashMap<String,Event>();
public void addLisenter(String eventType, EventListener target, Method callback){
events.put(eventType,new Event(target,callback));
}
public void addLisenter(String eventType, EventListener target){
try {
this.addLisenter(eventType, target, target.getClass().getMethod("on" + toUpperFirstCase(eventType), Event.class));
}catch (NoSuchMethodException e){
return;
}
}
private String toUpperFirstCase(String eventType) {
char [] chars = eventType.toCharArray();
chars[0] -= 32;
return String.valueOf(chars);
}
private void trigger(Event event){
event.setSource(this);
event.setTime(System.currentTimeMillis());
try {
if (event.getCallback() != null) {
//用反射调用回调函数
event.getCallback().invoke(event.getTarget(), event);
}
}catch (Exception e){
e.printStackTrace();
}
}
protected void trigger(String trigger){
if(!this.events.containsKey(trigger)){return;}
trigger(this.events.get(trigger).setTrigger(trigger));
}
}
// 观察类型
public interface MouseEventType {
String ON_CLICK = "click";
String ON_MOVE = "move";
}
/**
* 具体的被观察者
*/
public class Mouse extends EventContext {
public void click(){
System.out.println("调用单击方法");
this.trigger(MouseEventType.ON_CLICK);
}
public void move(){
System.out.println("调用移动方法");
this.trigger(MouseEventType.ON_MOVE);
}
}
/**
* 观察者
*/
public class MouseEventLisenter implements EventListener {
public void onClick(Event e){
System.out.println("==========触发鼠标单击事件========
" + e);
}
public void onMove(Event e){
System.out.println("==========触发鼠标移动事件========
" + e);
}
}
// 客户端代码
public class Test {
public static void main(String[] args) {
MouseEventLisenter lisenter = new MouseEventLisenter();
Mouse mouse = new Mouse();
mouse.addLisenter(MouseEventType.ON_CLICK,lisenter);
mouse.addLisenter(MouseEventType.ON_MOVE,lisenter);
mouse.click();
mouse.move();
}
}
三、实现一个异步非阻塞框架
我们前面介绍过,观察者模式的一个弊端就是需要一个个通知,如果其中一个观察者处理事件卡壳,会影响后续的观察者接收该事件。
1、EventBus
EventBus 翻译为“事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。其中,Google Guava EventBus 就是一个比较著名的 EventBus 框架,它不仅仅支持异步非阻塞模式,同时也支持同步阻塞模式。
2、使用MQ
使用MQ可以应用于分布式场景的发布订阅模型。
四、源码中的观察者模式
1、Observable/Observer
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
- void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
- void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
- void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
我们实现一个警察抓小偷的案例,警察是观察者,小偷是被观察者。代码如下:
// 小偷是一个被观察者,所以需要继承Observable类
public class Thief extends Observable {
private String name;
public Thief(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void steal() {
System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
super.setChanged(); //changed = true
super.notifyObservers();
}
}
// 警察是一个观察者,所以需要让其实现Observer接口
public class Policemen implements Observer {
private String name;
public Policemen(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
//创建小偷对象
Thief t = new Thief("隔壁老王");
//创建警察对象
Policemen p = new Policemen("小李");
//让警察盯着小偷
t.addObserver(p);
//小偷偷东西
t.steal();
}
}
2、Flow API
但是Observable和Observer在jdk1.9之后被弃用了。取而代之的是java.util.concurrent.Flow 类:
响应式编程详解,带你熟悉Reactor响应式编程
3、Spring中的Event
Spring事件详解,Spring-Event源码详解,一文搞透Spring事件管理