# 观察者模式(Observer Pattern)

目录: https://note.dolyw.com/design/ (opens new window)

代码地址

# 1. 介绍

观察者模式指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式模型-视图模式。观察者模式属于行为型模式

# 1.1. 特点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则
  • 目标与观察者之间建立了一套触发机制

# 1.2. 结构

  • 抽象目标(Subject):它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法
  • 具体目标(Concrete Subject):它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象
  • 抽象观察者(Observer):它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
  • 具体观察者(Concrete Observer):实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态

# 1.3. 优缺点

  • 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
  • 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作
  • 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知
  • 如何解决:使用面向对象技术,可以将这种依赖关系弱化
  • 关键代码:在抽象类里有一个 List 存放观察者们
  • 优点
    • 观察者和被观察者是抽象耦合的
    • 建立一套触发机制
  • 缺点
    • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,影响程序的效率
    • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃
    • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
  • 使用场景
    • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用
    • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度
    • 一个对象必须通知其他对象,而并不知道这些对象是谁
    • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制
  • 注意事项
    • JAVA 中已经有了对观察者模式的支持类
    • 避免循环引用
    • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式

# 1.4. 举例

  • 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价
  • 微信公众号与微信用户
  • 订阅博客,发布新博客通知订阅者

# 2. 代码v1

用订阅博客,发布新博客通知订阅者的例子,v1 版本是直接使用了 JAVA 中默认实现的抽象目标和抽象目标

# 2.1. 具体目标

/**
 * 具体目标,使用JDK提供的Observable类抽象目标
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/1/20 14:13
 */
public class BlogConcreteSubject extends Observable {

    private String name;

    public BlogConcreteSubject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    /**
     * 发布新文章,通知所有订阅了博客的人
     *
     * @param title
     * @return void
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2022/1/20 14:17
     */
    public void publish(String title) {
        // 设置标识位 changed = true,表示被观察者发生了改变
        setChanged();
        // 通知观察者,可以给观察者传递数据
        notifyObservers(title);
    }

}

# 2.2. 具体观察者

/**
 * 具体观察者,使用JDK提供的Observable类抽象观察者Observer
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/1/20 14:21
 */
public class SubscribeConcreteObserver implements Observer {

    private String name;

    public SubscribeConcreteObserver(String name) {
        this.name = name;
    }

    /**
     * 发布新博客后,订阅者接收到消息
     *
     * @param o
     * @param arg
     */
    @Override
    public void update(Observable o, Object arg) {
        BlogConcreteSubject blog = (BlogConcreteSubject) o;
        String title = (String) arg;
        System.out.println(name + "订阅的博客[" + blog.getName() + "]又发布了新文章-" + title);
    }
}

# 2.3. 执行

/**
 * 观察者模式,使用JDK的提供的类完成
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/1/20 14:02
 */
public class Main {

    public static void main(String[] args) {
        // 构建具体目标
        BlogConcreteSubject blogConcreteSubject = new BlogConcreteSubject("dolyw.com");
        // 添加观察者
        blogConcreteSubject.addObserver(new SubscribeConcreteObserver("王明"));
        blogConcreteSubject.addObserver(new SubscribeConcreteObserver("王美丽"));
        // 具体目标发送变化,发布新博客
        blogConcreteSubject.publish("母猪的产后护理");
    }

}

输出

王明订阅的博客[dolyw.com]又发布了新文章-母猪的产后护理
王美丽订阅的博客[dolyw.com]又发布了新文章-母猪的产后护理

# 3. 代码v2

用订阅博客,发布新博客通知订阅者的例子,v2 版本是使用原生方式实现

# 3.1. 抽象目标

/**
 * 抽象目标
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/1/20 14:03
 */
public abstract class AbstractSubject {

    protected List<AbstractObserver> observerList = new ArrayList<>();

    public synchronized void addObserver(AbstractObserver observer) {
        if (observer == null) {
            throw new NullPointerException();
        }
        if (!observerList.contains(observer)) {
            observerList.add(observer);
        }
    }

    public synchronized void deleteObserver(AbstractObserver observer) {
        observerList.remove(observer);
    }

    /**
     * 通知所有观察者
     *
     * @param arg
     */
    public void notifyObservers(Object arg) {
        for (AbstractObserver abstractObserver : observerList) {
            abstractObserver.update(this, arg);
        }
    }

    /**
     * 目标发生变化
     *
     * @param title
     */
    public abstract void publish(String title);

}

# 3.2. 具体目标

/**
 * 具体目标,使用原生方式实现
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/1/20 14:13
 */
public class BlogConcreteSubject extends AbstractSubject {

    private String name;

    public BlogConcreteSubject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    /**
     * 发布新文章,通知所有订阅了博客的人
     *
     * @param title
     * @return void
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2022/1/20 14:17
     */
    @Override
    public void publish(String title) {
        // 通知观察者,可以给观察者传递数据
        notifyObservers(title);
    }

}

# 3.3. 抽象观察者

/**
 * 抽象观察者
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/1/20 14:05
 */
public abstract class AbstractObserver {

    /**
     * 观察者接收通知
     *
     * @param abstractSubject
	 * @param arg
     * @return void
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2022/1/21 15:39
     */
    public abstract void update(AbstractSubject abstractSubject, Object arg);

}

# 3.4. 具体观察者

/**
 * 具体观察者,使用原生方式实现
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/1/20 14:21
 */
public class SubscribeConcreteObserver extends AbstractObserver {

    private String name;

    public SubscribeConcreteObserver(String name) {
        this.name = name;
    }

    /**
     * 订阅目标发布新博客后,订阅者接收到消息
     *
     * @param abstractSubject
     * @param arg
     */
    @Override
    public void update(AbstractSubject abstractSubject, Object arg) {
        BlogConcreteSubject blog = (BlogConcreteSubject) abstractSubject;
        String title = (String) arg;
        System.out.println(name + "订阅的博客[" + blog.getName() + "]又发布了新文章-" + title);
    }
}

# 3.5. 执行

/**
 * 观察者模式,使用原生方式完成
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/1/20 14:02
 */
public class Main {

    public static void main(String[] args) {
        // 构建具体目标
        AbstractSubject blogConcreteSubject = new BlogConcreteSubject("dolyw.com");
        // 添加观察者
        blogConcreteSubject.addObserver(new SubscribeConcreteObserver("王明"));
        blogConcreteSubject.addObserver(new SubscribeConcreteObserver("王美丽"));
        // 具体目标发送变化,发布新博客
        blogConcreteSubject.publish("Java入门到放弃");
    }

}

输出

王明订阅的博客[dolyw.com]又发布了新文章-Java入门到放弃
王美丽订阅的博客[dolyw.com]又发布了新文章-Java入门到放弃
上次更新时间: 2023-12-15 03:14:55