3

设计模式之观察者模式_程序员田同学的技术博客_51CTO博客

 1 year ago
source link: https://blog.51cto.com/u_15476035/5703551
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

观察者模式是极其重要的一个设计模式,也是我几年开发过程中使用最多的设计模式,本文首先概述观察者模式的基本概念和Demo实现,接着是观察者模式在Java和Spring中的应用,最后是对观察者模式的应用场景和优缺点进行总结。

一、概念理解

观察者模式:定义对象之间的一种一对多的依赖关系,使得每当一个对象的状态发生变化时,其相关的依赖对象都可以得到通知并被自动更新。主要用于多个不同的对象对一个对象的某个方法会做出不同的反应!

概念啥意思呢?也就是说,如果使用观察者模式在A的业务逻辑中调用B的业务逻辑,即使B的业务逻辑报错了,仍然不影响A的执行。

比如,在我最近公司开发商城系统的过程中,提交订单成功以后要删除购物车中的信息,如果我先写订单提交逻辑,接着写删除购物车逻辑,这样当然没有什么问题,但是这样程序的健壮性太差了。

应该将该业务分成两步,一是处理订单成功处理逻辑,二是删除购物车中的信息。即使删除购物车报错了,提交订单逻辑仍然不影响。

那应该怎么做才能让他们互不影响呢?需要在购物车对象中要有一个方法用于删除购物车,还要有一个对象A用于注入(add)购物车对象和通知(notify)购物车执行它的方法。

在执行时先调用对象A的add方法将购物车对象添加到对象A中,在订单提交成功以后,调用对象A的通知notify购物车方法执行清除购物车逻辑。

在观察者模式中,购物车就称为观察者,对象A就称为目标对象。在面向接口编程原则下,观察者模式应该包括四个角色:

1、目标接口(subject) :它是一个抽象类,也是所有目标对象的父类。它用一个列表记录当前目标对象有哪些观察者对象,并提供增加、删除观察者对象和通知观察者对象的方法声明。

2、具体目标类:可以有多个不同的具体目标类,它们同时继承Subject类。一个目标对象就是某个具体目标类的对象,一个具体目标类负责定义它自身的事务逻辑,并在状态改变时通知它的所有观察者对象。

3、观察者接口(Listener) 它也是一个抽象类,是所有观察者对象的父类;它为所有的观察者对象都定义了一个名为update(notify)的方法。当目标对象的状态改变时,它就是通过调用它的所有观察者对象的update(notify)方法来通知它们的。

4、具体观察者类,可以有多个不同的具体观察者类,它们同时继承Listener类。一个观察者对象就是某个具体观察者类的对象。每个具体观察者类都要重定义Listener类中定义的update(notify)方法,在该方法中实现它自己的任务逻辑,当它被通知的时候(目标对象调用它的update(notify)方法)就执行自己特有的任务。在我们的例子中是购物车观察者,当然还能有别的,如日志观察者。

我们基于四个角色实现demo。

二、案例实现

目标接口:包括注册、移除、通知监听者的方法声明。

/**
 * 这是被观察的对象
 * 目标类
 * @author tcy
 * @Date 17-09-2022
 */
public interface SubjectAbstract<T> {
    //注册监听者
    public void registerListener(T t);
    //移除监听者
    public void removeListener(T t);
    //通知监听者
    public void notifyListener();
}

目标接口实现:里面需要一个listenerList数组存储所有的观察者,需要定义add和remove观察者的方法,需要给出notify方法通知所有的观察者对象。

/**
 * 
 * 具体目标类
 * @author tcy
 * @Date 17-09-2022
 */
public class SubjectImpl implements SubjectAbstract<ListenerAbstract> {

    //监听者的注册列表
    private List<ListenerAbstract> listenerList = new ArrayList<>();

    @Override
    public void registerListener(ListenerAbstract myListener) {
        listenerList.add(myListener);
    }

    @Override
    public void removeListener(ListenerAbstract myListener) {
        listenerList.remove(myListener);
    }

    @Override
    public void notifyListener() {
        for (ListenerAbstract myListener : listenerList) {
            System.out.println("收到推送事件,开始调用异步逻辑...");
            myListener.onEvent();
        }
    }
}

观察者接口:声明响应方法

/**
 * 
 * 观察者-接口
 * @author tcy
 * @Date 17-09-2022
 */
public interface ListenerAbstract {
    void onEvent();
}

观察者接口:实现响应方法,处理清除购物车的逻辑。

/**
 * 具体观察者类 购物车
 * @author tcy
 * @Date 17-09-2022
 */
public class ListenerMyShopCart implements ListenerAbstract {
    @Override
    public void onEvent() {

            //...省略购物车处理逻辑
            System.out.println("删除购物车中的信息...");

    }
}

我们使用Client模拟提交订单操作。

/**
 * 先使用具体目标对象的registerListener方法添加具体观察者对象,
 * 然后调用其notify方法通知观察者
 * @author tcy
 * @Date 17-09-2022
 */
public class Client {
    public static void main(String[] args) {

        System.out.println("订单成功处理逻辑...");
        //创建目标对象
        SubjectImpl subject=new SubjectImpl();

        //具体观察者注册入 目标对象
        ListenerMyShopCart shopCart=new ListenerMyShopCart();
        //向观察者中注册listener
        subject.registerListener(shopCart);

        //发布事件,通知观察者
        subject.notifyListener();

    }
}

这样就实现了订单的处理逻辑和购物车的逻辑解耦,即使购物车逻辑报错也不会影响订单处理逻辑。

既然观察者模式是很常用的模式,而且抽象观察者和抽象目标类方法声明都是固定的,作为高级语言Java,Java设计者干脆内置两个接口,开发者直接实现接口就能使用观察者模式。

三、Java中的观察者模式

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义观察者模式,只要实现它们的子类就可以编写观察者模式实例。

Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。

void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。

void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。

我们基于Java的两个接口,改造我们的案例。

具体目标类:

/**
 * 具体目标类 
 * @author tcy
 * @Date 19-09-2022
 */
public class SubjectObservable extends Observable {

    public void notifyListener() {
        super.setChanged();
        System.out.println("收到推送的消息...");
        super.notifyObservers();    //通知观察者购物车事件
    }

}

具体观察者类:

/**
 * 观察者实现类
 * @author tcy
 * @Date 19-09-2022
 */
public class ShopCartObserver implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("清除购物车...");

    }
}

依旧是Client模拟订单处理逻辑。

/**
 * @author tcy
 * @Date 19-09-2022
 */
public class Client {
    public static void main(String[] args) {
        System.out.println("订单提交成功...");
        SubjectObservable observable = new SubjectObservable();

        Observer shopCartObserver = new ShopCartObserver(); //购物车

        observable.addObserver(shopCartObserver);
        observable.notifyListener();


    }

}

这样也能实现观察者逻辑,但Java中的观察者模式有一定的局限性。

Observable是个类,而不是一个接口,没有实现Serializable,所以,不能序列化和它的子类,而且他是线程不安全的,无法保证观察者的执行顺序。在JDK9之后已经启用了。

写Java的恐怕没有不用Spring的了,作为优秀的开源框架,Spring中也有观察者模式的大量应用,而且Spring是在java的基础之上改造的,很好的规避了Java观察者模式的不足之处。

四、Spring如何使用观察者模式

在第一章节典型的观察者模式中包含着四个角色:目标类、目标类实现、观察者、观察者实现类。而在Spring下的观察者模式略有不同,Spring对其做了部分改造。

事件:

Spring中定义最顶层的事件ApplicationEvent,这个接口最终还是继承了EventObject接口。

设计模式之观察者模式_spring

只是在基础之上增加了构造和获取当前时间戳的方法,Spring所有的事件都要实现这个接口,比如Spring中内置的ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent…看名字大概就知道这些事件用于哪些地方,分别是容器刷新后、开始时、停止时…

目标类接口:

Spirng中的ApplicationEventMulticaster接口就是实例中目标类,我们可以对比我们的目标接口和ApplicationEventMulticaster接口,长的非常像。

设计模式之观察者模式_观察者模式_02

观察者接口:

观察者ApplicationListener用于监听事件,只有一个方法onApplicationEvent事件发生后该事件执行。与我们样例中的抽象观察者并无太大的不同。

目标类实现:

在我们案例中目标类的职责直接在一个类中实现,注册监听器、广播事件(调用监听器方法)。

在Spring中两个实现类分别拆分开来,Spring启动过程中会调用registerListeners()方法,看名字我们大概就已经知道是注册所有的监听器,该方法完成原目标类的注册监听器职责。

在Spring中事件源ApplicationContext用于广播事件,用户不必再显示的调用监听器的方法,交给Spring调用,该方法完成原目标类的广播事件职责。

我们基于Spring的观察者模式继续改造我们的案例。

购物车事件:

/**
 * 购物车事件
 * @author tcy
 * @Date 19-09-2022
 */
@Component
public class EventShopCart extends ApplicationEvent {

    private String orderId;

    public EventShopCart(Object source, String orderId) {
        super(source);
        this.orderId=orderId;
    }


    public EventShopCart() {
        super(1);
    }
}

发布者(模拟Spring调用监听器的方法,实际开发不需要写):

/**
 * 发布者
 * @author tcy
 * @Date 19-09-2022
 */
@Component
public class MyPublisher implements ApplicationContextAware {
    private ApplicationContext applicationContext;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 发布事件
     * 监听该事件的监听者都可以获取消息
     *
     * @param myEvent
     */
    public void workEvent(EventShopCart myEvent) {
        //该方法会调用监听器实现的方法
        applicationContext.publishEvent(myEvent);
    }
}
/**
 * 监听者
 * @author tcy
 * @Date 19-09-2022
 */
@Component
public class ListenerShopCart implements ApplicationListener<EventShopCart> {
    @Override
    public void onApplicationEvent(EventShopCart myEvent) {
        System.out.println("清除购物车成功...");
    }
}

Client模拟调用:

/**
 * @author tcy
 * @Date 19-09-2022
 */
public class Client {

    public static void main(String[] args) {
        ApplicationContext ac =new AnnotationConfigApplicationContext("cn.sky1998.behavior.observer.spring");

        System.out.println("订单提交成功...");
        MyPublisher bean = ac.getBean(MyPublisher.class);
        EventShopCart myEvent = ac.getBean(EventShopCart.class);

        bean.workEvent(myEvent);
    }
}

通过Spring实现观察者模式比我们手动写简单的多。

使用Spring实现观察者模式时,观察者接口、目标接口、目标实现,我们都不需要管,只负责继承ApplicationEvent类定义我们自己的事件,并实现ApplicationListener<自定义事件>接口实现我们的观察者,并在对应的业务中调用applicationContext.publishEvent(new ShopCartEvent(cmOrderItemList)),即实现了观察者模式。

读者可以拉取完整代码本地学习,实现代码均测试通过上传到 码云

Spring使用观察者模式我在很久之前就使用过,但是并不清楚为什么要这样写,学了观察者模式以后,写起来变得通透多了。

虽然观察者模式的概念是:一对多的依赖关系,但不一定观察者有多个才能使用,我们的例子都是使用的一个观察者。

它很好的降低了目标与观察者之间的耦合关系,目标与观察者建立一套触发机制,也让他成为了最常见的设计模式。

设计模式的学习要成体系,推荐你看我往期发布的设计模式文章。

 一、设计模式概述

 二、设计模式之工厂方法和抽象工厂

 三、设计模式之单例和原型

 四、设计模式之建造者模式

 五、设计模式之代理模式

 六、设计模式之适配器模式

 七、设计模式之桥接模式

 八、设计模式之组合模式

 九、设计模式之装饰器模式

 十、设计模式之外观模式

 十一、外观模式之享元模式

 十二、设计模式之责任链模式

 十三、设计模式之命令模式

 十四、设计模式之解释器模式

 十五、设计模式之迭代器模式

 十六、设计模式之中介者模式

 十七、设计模式之备忘录模式


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK