观察者模式

简介

所谓观察者模式,就是一旦观察的对象发生了改变,立马通知所有观察者做出相应的改变。是不是和发布订阅很像,因此观察者模式又叫发布订阅模式。

观察者使用范围十分广泛,比如我们关注的微信公众号有新内容,订阅的用户会有提示;又比如你在起点上观看的小说有更新,也会有消息提醒。

而一般的观察者模式由观察目标、抽象观察者、具体观察者构成。其中观察目标就可以理解为用户关注的公众号。

代码实现

初版代码实现

这里的初版代码我就直接使用菜鸟教程的代码了。

定义观察对象

/**
 * 观察目标类,观察目标类必须要知道所有订阅自己的观察者,不然无法发送更新通知给观察者们
 *
 * @author zxb 2023/6/22 9:20
 */
public class Subject {

    private List<Observe> observes = new ArrayList<>();

    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        // 更新后通知所有观察者
        notifyAllObserve();
    }

    /**
     * 添加观察者
     *
     * @param observe 观察者
     */
    public void attach(Observe observe) {
        observes.add(observe);
    }

    /**
     * 删除观察者
     *
     * @param observe 观察者
     */
    public void remove(Observe observe) {
        observes.remove(observe);
    }

    /**
     * 通知所有观察者,观察目标属性发生变更
     */
    public void notifyAllObserve() {
        for (Observe observe : observes) {
            observe.update();
        }
    }
}

观察目标核心就是三个方法,一个观察者感兴趣的状态,其它可以根据具体业务扩充。

定义抽象观察者

/**
 * 抽象观察者类
 *
 * @author zxb 2023/6/22 9:22
 */
public abstract class Observe {
    protected Subject subject;

    public abstract void update();
}

抽象观察者就是要提供一个更新方法给观察目标调用。

定义具体观察者

这里有三个具体的观察者实现类。

package org.example.observe;

/**
 * @author zxb 2023/6/22 9:29
 */
public class BinaryObserver extends Observe {
    public BinaryObserver(Subject subject) {
        this.subject = subject;
        subject.attach(this);
    }
    @Override
    public void update() {
        System.out.println("BinaryObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(subject.getState()));
    }
}
package org.example.observe;

/**
 * @author zxb 2023/6/22 9:29
 */
public class OctalObserver extends Observe {
    public OctalObserver(Subject subject) {
        this.subject = subject;
        subject.attach(this);
    }

    @Override
    public void update() {
        System.out.println("OctalObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toOctalString(subject.getState()));
    }
}

package org.example.observe;

/**
 * @author zxb 2023/6/22 9:29
 */
public class HexObserver extends Observe {
    public HexObserver(Subject subject) {
        this.subject = subject;
        subject.attach(this);
    }

    @Override
    public void update() {
        System.out.println("HexObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toHexString(subject.getState()));
    }
}

上面三个观察者分别感知观察对象的 state 状态,一但发生变化,即打印更新的内容,分别是二进制、八进制、十六进制打印。

单元测试

package org.example.observe;

/**
 * @author zxb 2023/6/22 9:37
 */
public class ObserveTest1 {
    public static void main(String[] args) {
        // 创建被感知的对象
        Subject subject = new Subject();
        // 创建观察者们
        new BinaryObserver(subject);
        new OctalObserver(subject);
        new HexObserver(subject);
        // 观察目标发生更新
        subject.setState(1);
        subject.setState(2);
    }
}

执行结果如下:
执行结果
可以看到,一但观察目标发生更新,所有观察者都会做出相应的变化。这就是观察者模式的提现。
在很多观察者模式的博客中,都会提到观察者模式容易出现循环依赖的情况,那么下面就来复现下循环依赖。

循环依赖代码实现

如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
那么将上面的代码稍微改一下。
初版代码实现中抽象观察者关联了观察目标的引用,该引用被各自具体观察者的构造方法入参赋值。
那么如果错误编码,导致在具体观察者的构造方法中直接实例化观察对象,而不是通过入参赋值。并且,在观察对象的构造方法中实例化具体的观察者,然后调用添加观察者方法向容器中添加观察者,这样就会出现循环依赖问题。
循环依赖代码如下:

定义观察目标

package org.example.observe_circle_rely;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zxb 2023/6/22 9:20
 */
public class Subject {


    public Subject() {
    	// 直接实例化观察者,并将观察者加入观察者容器。
        Observe binaryObserver = new BinaryObserver();
        Observe octalObserver = new OctalObserver();
        Observe hexObserver = new HexObserver();
        attach(binaryObserver);
        attach(octalObserver);
        attach(hexObserver);
    }

    private List<Observe> observes = new ArrayList<>();

    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        notifyAllObserve();
    }

    /**
     * 添加观察者
     *
     * @param observe 观察者
     */
    public void attach(Observe observe) {
        observes.add(observe);
    }

    /**
     * 删除观察者
     *
     * @param observe 观察者
     */
    public void remove(Observe observe) {
        observes.remove(observe);
    }

    public void notifyAllObserve() {
        for (Observe observe : observes) {
            observe.update();
        }
    }
}

定义抽象观察者

抽象观察者不变。和初版代码实现一致。

定义具体观察者

package org.example.observe_circle_rely;

/**
 * @author zxb 2023/6/22 9:29
 */
public class BinaryObserver extends Observe {

    public BinaryObserver() {
    	// 在构造方法中实例化观察目标。并在 update 方法中试图获取观察目标的值
        subject = new Subject();
    }

    @Override
    public void update() {
        System.out.println("BinaryObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(subject.getState()));
    }
}

其余具体观察者和 BinaryObserver 一致,都是在构造方法中实例化了观察目标,就不将代码贴出来了。

单元测试

package org.example.observe_circle_rely;

/**
 * @author zxb 2023/6/22 9:37
 */
public class ObserveTest1 {
    public static void main(String[] args) {
        // 创建被感知的对象
        Subject subject = new Subject();
        // 由于向观察者容器中添加观察者对象的操作在 Subject 构造方法中做了,因此不用显示调用
        subject.setState(1);

    }
}

执行结果出现循环依赖:
循环依赖结果

这就是当观察者和观察目标直接互相严重依赖导致的问题。因此在使用观察者模式时,要注意这个问题。

最终代码实现

初版代码能够实现基本的观察者模式,但是在单元测试中,我们需要去手动创建 N 多个观察者,稍显麻烦。
并且,在抽象观察者中,关联了观察对象的引用,其实并不需要,我们只需要感知观察对象状态变更的结果就行。
且现在都是基于 Spring 环境开发,我们可以利用 Spring 的 DI 特性来简化代码。
具体代码如下:

定义观察对象

package com.example.just_boot.observe;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 观察目标类,观察目标类必须要知道所有订阅自己的观察者,不然无法发送更新通知给观察者们
 *
 * @author zxb 2023/6/22 9:20
 */
@Component
public class Subject implements ApplicationContextAware {

    private List<Observe> observes = new ArrayList<>();

    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        // 更新后通知所有观察者
        notifyAllObserve();
    }

    /**
     * 添加观察者
     *
     * @param observe 观察者
     */
    public void attach(Observe observe) {
        observes.add(observe);
    }

    /**
     * 删除观察者
     *
     * @param observe 观察者
     */
    public void remove(Observe observe) {
        observes.remove(observe);
    }

    /**
     * 通知所有观察者,观察目标属性发生变更
     */
    public void notifyAllObserve() {
        for (Observe observe : observes) {
       		// 将更新后的结果告知观察者们
            observe.update(getState());
        }
    }

    /**
     * 从容器中获取所有观察者,在 IOC 容器启动时将观察者放入观察者容器。
     *
     * @param applicationContext IOC 容器
     * @throws BeansException exception
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        String[] beanNamesForType = applicationContext.getBeanNamesForType(Observe.class);
        if (ArrayUtils.isNotEmpty(beanNamesForType)) {
            for (String beanName : beanNamesForType) {
                Observe bean = (Observe) applicationContext.getBean(beanName);
                attach(bean);
            }
        }
    }
}

定义抽象观察者

抽象观察者不再关联观察结果对象,而是将观察结果变更的属性通过入参传入。

package com.example.just_boot.observe;

/**
 * 抽象观察者类
 *
 * @author zxb 2023/6/22 9:22
 */
public abstract class Observe {
    public abstract void update(Integer result);
}

定义具体观察者

package com.example.just_boot.observe;

import org.springframework.stereotype.Component;

/**
 * @author zxb 2023/6/22 9:29
 */
@Component
public class BinaryObserver extends Observe {

    @Override
    public void update(Integer result) {
        System.out.println("BinaryObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(result));
    }
}
package com.example.just_boot.observe;

import org.springframework.stereotype.Component;

/**
 * @author zxb 2023/6/22 9:29
 */
@Component
public class OctalObserver extends Observe {
    @Override
    public void update(Integer result) {
        System.out.println("OctalObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(result));
    }
}
package com.example.just_boot.observe;

import org.springframework.stereotype.Component;

/**
 * @author zxb 2023/6/22 9:29
 */
@Component
public class HexObserver extends Observe {

    @Override
    public void update(Integer result) {
        System.out.println("HexObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(result));
    }
}

定义 Controller

package com.example.just_boot.controller;

import com.example.just_boot.observe.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author zxb 2023/6/22 13:09
 */
@RestController
@RequestMapping(path = "/observe")
public class ObserveController {

    @Resource
    private Subject subject;

    @GetMapping(path = "/")
    public String testUpdate() {
        subject.setState(15);
        return "success";
    }
}

访问 http://localhost:8080/observe/, 页面返回 success。控制台打印如下:
执行结果

总结

通过上面的迭代更新,观察者模式的代码已初显威力,但是也还有优化的地方。比如观察者对象也可以定义一个接口,定义通用的方法规范,然后定义实现类进行扩充。
设计模式代码风格多变,但是核心思想不变。
观察者模式代码的核心思想就是:
● 定义观察目标对象,在该对象中定义:添加观察者方法删除观察者方法通知所有观察者方法更新自身状态的方法(观察者观察的属性)。此外,该对象中还需要有一个观察者容器,用来存放所有观察此对象的观察者。这个观察者容器必须要有,不然当观察目标发生变化,无法通知到所有观察此对象的观察者们。
● 定义抽象观察者类,该对象中一般定义一个更新方法,用于观察目标自身属性发生更新,调用此更新方法通知观察者。
● 定义具体观察者类,这里具体的观察者类一般有多个,他们在更新方法中实现当观察目标属性变更时做出的具体逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值