设计模式(六):观察者模式(发布订阅模式)

首先来说一下什么是观察者模式,观察者模式其实跟消息发送器差不多,观察者觉察到了事件发生,就去通知其他人该事件,说白了担任的角色跟晚修课上时刻注意级长来了的同学一样

双向耦合

假如我们去实现观察者模式的时候,通常需要两个部分

  • 观察者:观察事件是否发生,其实本质上是监听通知者
  • 通知者:通知者要去通知观察者事件发生

这就涉及到一个双向耦合的问题,当新增一个观察者,或者新增一个通知者时,两方都要进行修改的!

  • 新增一个观察者时,该观察者需要去重新认识所有的通知者,且所有的通知者也要去重新认识这个观察者,否则无法进行正常的发送和接受事件动作
  • 新增一个被观察者时,通知者要去重新认识所有的观察者,且所有的观察者也要去重新认识这个通知者

双向耦合问题就违反了两个原则了

  • 开放封闭原则:重新认识的过程,会导致修改了原有的代码!
  • 依赖倒转原则:程序之间互相依赖,而不是去依赖抽象!

观察者模式

对于双向耦合问题,我们可以通过抽象通知者与观察者。

一般来说,通知者与观察者的关系都是一对多

下面就用一个栗子去看看观察者模式代码如何写

假设场景:前台小姐姐帮公司摸鱼的员工看老板回来了没有,回来了就通知摸鱼的员工

在这个栗子中,前台小姐姐就是通知者,而摸鱼的员工则是观察者

观察者抽象类

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();
    }
}

在这里插入图片描述

总结一下

  1. 所谓通知的过程,其实本质上是由通知者去调用观察者而已
  2. 这里的栗子可能使用抽象类来说明不太合适,因为通知者是一个功能性角色,而不是一个模板,使用接口去替换比较好
  3. 观察者模式定义了一种一对多的依赖关系,相当于让多个观察者对象去监听通知者,通知者一旦自身感知状态发生变化,理解通知所有观察者对象去执行动作(调用观察者)
  4. 观察者模式通过对观察者和通知者进行抽象,实现了依赖翻转(依赖抽象),开放封闭(不用改变源代码)

什么时候可以使用观察者模式呢?

观察者模式的功能其实就是解耦合,然互相依赖的双方都抽象起来,从依赖具体变成了依赖抽象!使得双方之间的变化都让对方不可见!

适用场景

  1. 当改变一个对象会涉及到另外一个对象改变,使用观察者模式可以进行解耦!
  2. 当一个系统需要几个对象去进行协同合作,并且相关对象之间需要维持一致性,也可以使用观察者模式解耦合

观察者模式的不足

虽然观察者模式剋实现依赖翻转、开放封闭,但没有解决一个根源性的问题,那就是通知者与观察者之间仍然存在着联系!即观察者与通知者仍然需要互相依赖,万一没有了观察者或通知者接口,这个模式就会出现问题!

那怎么办呢?

只要观察者和通知者之间根本就互相不知道,由客户端去告诉通知者, 你去观察这类观察者,那不就可以了吗?

通俗一点来说就是,将观察者与通知者之间的认知去除,并且将认知过程延迟到由客户端去实现

事件委托实现

具体实现逻辑是这样的

  • 对通知者类进行抽象
  • 需要抽象观察者,因为认识过程去除,所以通知者与观察者是相互间没有认知的,但仍然需要抽象观察者去提供统一的事件委托出去
  • 观察者将自己的事件委派给通知者,通知者接收到信息后直接调用观察者委派过来的事件(模仿通知、调用的过程)
  • 本质上,就是使用事件的方式来代替通知者与观察者的认知,即通知者拿到事件,直接调用事件,而该事件的来源为观察者

在这里插入图片描述
不过由于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++里面的函数指针,相当于把函数交给通知者去调用,替换了之前把观察者交给通知者去调用,完全去除了观察者与通知者之间的依赖性!交由客户端进行委托事件即可

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值