首先来说一下什么是观察者模式,观察者模式其实跟消息发送器差不多,观察者觉察到了事件发生,就去通知其他人该事件,说白了担任的角色跟晚修课上时刻注意级长来了的同学一样
双向耦合
假如我们去实现观察者模式的时候,通常需要两个部分
- 观察者:观察事件是否发生,其实本质上是监听通知者
- 通知者:通知者要去通知观察者事件发生
这就涉及到一个双向耦合的问题,当新增一个观察者,或者新增一个通知者时,两方都要进行修改的!
- 新增一个观察者时,该观察者需要去重新认识所有的通知者,且所有的通知者也要去重新认识这个观察者,否则无法进行正常的发送和接受事件动作
- 新增一个被观察者时,通知者要去重新认识所有的观察者,且所有的观察者也要去重新认识这个通知者
双向耦合问题就违反了两个原则了
- 开放封闭原则:重新认识的过程,会导致修改了原有的代码!
- 依赖倒转原则:程序之间互相依赖,而不是去依赖抽象!
观察者模式
对于双向耦合问题,我们可以通过抽象通知者与观察者。
一般来说,通知者与观察者的关系都是一对多
下面就用一个栗子去看看观察者模式代码如何写
假设场景:前台小姐姐帮公司摸鱼的员工看老板回来了没有,回来了就通知摸鱼的员工
在这个栗子中,前台小姐姐就是通知者,而摸鱼的员工则是观察者
观察者抽象类
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 12:04
* @Description: 观察者抽象类
*/
public abstract class Observe {
//认识的通知者
protected Notify notify;
//观察者的名字
protected String myName;
public Observe() {
}
public Observe(Notify notify, String myName) {
this.notify = notify;
this.myName = myName;
}
/**
* 接受消息,并且做的事情
*/
public abstract void doThing();
}
通知者抽象类
package com.mjh.design.day1.observe;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Ember
* @Date: 2021/9/16 12:06
* @Description: 通知者抽象类
*/
public abstract class Notify {
/**
* 认识的所有观察者
*/
protected List<Observe> observers;
public Notify(List<Observe> observers) {
this.observers = observers;
}
public Notify() {
this.observers = new ArrayList<Observe>();
}
/**
* 添加观察者
* @param observe
*/
public void addObserve(Observe observe){
this.observers.add(observe);
}
/**
* 通知观察者,延后到子类实现
*/
public abstract void doNotify();
}
具体的摸鱼员工,下面创建了两个员工
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 12:15
* @Description: NBA观察者
*/
public class NBAObserve extends Observe {
public NBAObserve() {
}
public NBAObserve(Notify notify, String myName) {
super(notify, myName);
}
@Override
public void doThing() {
System.out.println("老板来了,"+this.myName+"不能再看NBA了");
}
}
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 12:16
* @Description: 打游戏观察者
*/
public class PlayGameObserve extends Observe{
public PlayGameObserve() {
}
public PlayGameObserve(Notify notify, String myName) {
super(notify, myName);
}
@Override
public void doThing() {
System.out.println("老板来了,"+this.myName+"我不能再打游戏了!");
}
}
接下来再看前台小姐姐
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 12:13
* @Description: 秘书小姐姐
*/
public class Secretary extends Notify{
public Secretary() {
super();
}
/**
* 重写通知观察者
*/
@Override
public void doNotify() {
this.observers.forEach((item ->{
//相当于告诉观察者,事件发生了,你要干活了
//其实告诉这个动作就是一个调用过程!
item.doThing();
}));
}
}
这样就完成了观察者模式了
下面来让Client端去调用一下
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 12:17
* @Description:
*/
public class Client {
public static void main(String[] args) {
//实例一个通知者
Notify notify = new Secretary();
//实例所有观察者
Observe nbaObserve = new NBAObserve(notify, "nba");
Observe playGameObserve = new PlayGameObserve(notify,"LOL");
//通知者去认识观察者
notify.addObserve(nbaObserve);
notify.addObserve(playGameObserve);
//发生事件了,通知者去告诉所有观察者
notify.doNotify();
}
}
总结一下
- 所谓通知的过程,其实本质上是由通知者去调用观察者而已
- 这里的栗子可能使用抽象类来说明不太合适,因为通知者是一个功能性角色,而不是一个模板,使用接口去替换比较好
- 观察者模式定义了一种一对多的依赖关系,相当于让多个观察者对象去监听通知者,通知者一旦自身感知状态发生变化,理解通知所有观察者对象去执行动作(调用观察者)
- 观察者模式通过对观察者和通知者进行抽象,实现了依赖翻转(依赖抽象),开放封闭(不用改变源代码)
什么时候可以使用观察者模式呢?
观察者模式的功能其实就是解耦合,然互相依赖的双方都抽象起来,从依赖具体变成了依赖抽象!使得双方之间的变化都让对方不可见!
适用场景
- 当改变一个对象会涉及到另外一个对象改变,使用观察者模式可以进行解耦!
- 当一个系统需要几个对象去进行协同合作,并且相关对象之间需要维持一致性,也可以使用观察者模式解耦合
观察者模式的不足
虽然观察者模式剋实现依赖翻转、开放封闭,但没有解决一个根源性的问题,那就是通知者与观察者之间仍然存在着联系!即观察者与通知者仍然需要互相依赖,万一没有了观察者或通知者接口,这个模式就会出现问题!
那怎么办呢?
只要观察者和通知者之间根本就互相不知道,由客户端去告诉通知者, 你去观察这类观察者,那不就可以了吗?
通俗一点来说就是,将观察者与通知者之间的认知去除,并且将认知过程延迟到由客户端去实现
事件委托实现
具体实现逻辑是这样的
- 对通知者类进行抽象
- 需要抽象观察者,因为认识过程去除,所以通知者与观察者是相互间没有认知的,但仍然需要抽象观察者去提供统一的事件委托出去
- 观察者将自己的事件委派给通知者,通知者接收到信息后直接调用观察者委派过来的事件(模仿通知、调用的过程)
- 本质上,就是使用事件的方式来代替通知者与观察者的认知,即通知者拿到事件,直接调用事件,而该事件的来源为观察者
不过由于Java没有原生的委托事件Event,所以要自己手写一个
委托事件说白就是代表一个函数方法而已,我们利用反射+动态代理可以实现
委托事件类的实现
package com.mjh.design.day1.observe;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @Author: Ember
* @Date: 2021/9/16 14:24
* @Description: 委托事件
*/
public class EventHandler {
/**
* 代理的对象
*/
private Object domain;
/**
* 进行代理的方法名称
*/
private String methodName;
/**
* 所需参数
*/
private Object[] params;
/**
* 所需参数的class
*/
private Class[] paramTypes;
public EventHandler(){}
public EventHandler(Object domain,Object[] params,String methodName){
this.domain = domain;
this.methodName = methodName;
packageParams(params);
}
/**
* 封装参数与参数的class
* @param params
*/
private void packageParams(Object[] params){
this.params = params;
//获取参数的class
Class[] classes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
classes[i] = params[i].getClass();
}
}
public void invoke() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//使用反射去获取代理的方法
Method method = this.domain.getClass().getMethod(this.methodName, this.paramTypes);
//利用反射机制进行调用
method.invoke(this.domain,params);
}
}
接下来回归我们抽象的通知者
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 16:20
* @Description: 抽象通知者
*/
public interface NotifySecond {
/**
* 通知
* @throws Exception
*/
public void doNotify() throws Exception;
/**
* 添加委托事件
* @param eventHandler
*/
public void addEvent(EventHandler eventHandler);
}
新的通知者实体
package com.mjh.design.day1.observe;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Ember
* @Date: 2021/9/16 16:31
* @Description: 秘书
*/
public class SecretaryTwo implements NotifySecond{
/**
* 用事件去代替观察者
*/
private List<EventHandler> eventHandler;
SecretaryTwo(){
this.eventHandler = new ArrayList<EventHandler>();
}
/*
* 执行所有委托的事件
*/
@Override
public void doNotify() throws Exception{
eventHandler.forEach(event->{
try {
event.invoke();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}
/*
* 去添加委托事件
*/
@Override
public void addEvent(EventHandler eventHandler) {
this.eventHandler.add(eventHandler);
}
}
新的观察者
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 16:37
* @Description: 抽象的观察者
*/
public interface ObserveSecond {
/**
* 接受通知者发送的信息并做点什么
*/
public void doSomething();
}
新的摸鱼员工
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 16:38
* @Description: 看NBA的摸鱼员工
*/
public class NBAObserveTwo implements ObserveSecond{
@Override
public void doSomething() {
System.out.println("老板来了,不可以再看NBA了");
}
}
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 16:39
* @Description: 玩LOL的摸鱼员工
*/
public class LOLObserve implements ObserveSecond{
@Override
public void doSomething() {
System.out.println("老板来了,不能再玩LOL了");
}
}
接下来看看客户端如何去控制事件委托的
package com.mjh.design.day1.observe;
/**
* @Author: Ember
* @Date: 2021/9/16 16:35
* @Description:
*/
public class ClientTwo {
public static void main(String[] args) throws Exception {
//实例所有观察者
ObserveSecond nbaObserve = new NBAObserveTwo();
ObserveSecond lolObserve = new LOLObserve();
//实例通知者
NotifySecond secretary = new SecretaryTwo();
//客户端将事件委托给通知者
secretary.addEvent(new EventHandler(nbaObserve,new Object[0],"doSomething"));
secretary.addEvent(new EventHandler(lolObserve,new Object[0],"doSomething"));
//老板来了 进行通知
secretary.doNotify();
}
}
委托事件本质
委托事件可以理解为C++里面的函数指针,相当于把函数交给通知者去调用,替换了之前把观察者交给通知者去调用,完全去除了观察者与通知者之间的依赖性!交由客户端进行委托事件即可