👉文章示例代码👈

定义

定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者(观察者)都会收到通知并且自动更新。

观察者模式提供了一种对象设计,让观察者和被观察者(主题)之间松耦合。

四个角色

观察者模式的主要角色有以下四个:

  • 抽象主题角色Subject:将所欲对观察者对象的引用保存在一个集合里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加或者删除观察者对象。
  • 具体主题角色Concrete Subject:将有关状态存入具体观察者对象。在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • 抽象观察者角色Observer:给所有的具体观察者定义一个接口,在得到主题的通知时更新自己。
  • 具体观察者角色Concrete Observer:实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相互协调。

场景示例

最近新冠肆虐,药店的口罩很是畅销,甚至是脱销,大家都等着药店进口罩的通知。笔者以此作为示例。

方便起见,笔者这里使用Java内置的观察者模式。

在 ​java.util 包当中包含了最基本的 ​Observer 接口和 ​Observable 类,这与我们的抽象主题 ​Subject 和抽象观察者 ​Observer 很是相似。而且由于 Observer 接口和 Observable 类已经预先实现了许多功能,使得我们在使用上更加的方便。

创建具体主题

这里方法中调用的 ​setChanged() 方法和 ​notifyObservers() 方法都是由 ​Observable 类提供的。 setChanged() 代表着主题状态的改变。 ​notifyObservers() 则表示通知所有的注册了的观察者,参数可传可不传。

/**
 * @author zhh
 * @description 药店类
 * @date 2020-03-01 23:15
 */
public class Pharmacy extends Observable {

    /**
     * 药店名称
     */
    private String name;

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

    public String getName() {
        return name;
    }

    /**
     * 采购
     * @param pharmacy  药店
     * @param gauzeMask 口罩
     */
    public void purchase(Pharmacy pharmacy, GauzeMask gauzeMask) {
        System.out.println(String.format("%s最近刚采购了一批%s, 数量为%s, 单价为%s", pharmacy.getName(), gauzeMask.getType(),
                gauzeMask.getAmount(), gauzeMask.getPrice()));

        // Observable 提供的方法, 代表主题状态的改变
        setChanged();

        // Observable 提供的方法, 通知所有观察者
        notifyObservers(gauzeMask);
    }
}

创建具体观察者

update() 是父类抽象观察者 ​Obserber 中定义的方法。参数 ​Observable o 代表的是被观察的对象, ​Object arg 表示的是主题中发布通知时传递的对象。

/**
 * @author zhh
 * @description 顾客类
 * @date 2020-03-01 23:20
 */
public class Customer implements Observer {

    /**
     * 姓名
     */
    private String name;

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

    public void update(Observable o, Object arg) {
        Pharmacy pharmacy = (Pharmacy) o;
        GauzeMask gauzeMask = (GauzeMask) arg;
        System.out.println(String.format("顾客%s收到%s的通知: 最近刚采购了一批%s, 数量为%s, 单价为%s", this.name, pharmacy.getName(),
                gauzeMask.getType(), gauzeMask.getAmount(), gauzeMask.getPrice()));
    }
}

创建缺省实体类

/**
 * @author zhh
 * @description 口罩类
 * @date 2020-03-01 23:17
 */
public class GauzeMask {

    /**
     * 种类
     */
    private String type;

    /**
     * 数量
     */
    private int amount;

    /**
     * 价格
     */
    private double price;

    public GauzeMask(String type, int amount, double price) {
        this.type = type;
        this.amount = amount;
        this.price = price;
    }

    public String getType() {
        return type;
    }

    public int getAmount() {
        return amount;
    }

    public double getPrice() {
        return price;
    }
}

测试类及输出

/**
 * @author zhh
 * @description 测试类
 * @date 2020-03-01 23:46
 */
public class Test {

    public static void main(String[] args) {
        Pharmacy pharmacy = new Pharmacy("一方大药房");
        GauzeMask gauzeMask = new GauzeMask("一次性口罩", 5000, 4.8);

        Customer customer1 = new Customer("海豪");
        Customer customer2 = new Customer("亚萍");
        pharmacy.addObserver(customer1);
        pharmacy.addObserver(customer2);

        pharmacy.purchase(pharmacy, gauzeMask);
    }
}

测试类的输出结果如下:

一方大药房最近刚采购了一批一次性口罩, 数量为5000, 单价为4.8

顾客亚萍收到一方大药房的通知: 最近刚采购了一批一次性口罩, 数量为5000, 单价为4.8

顾客海豪收到一方大药房的通知: 最近刚采购了一批一次性口罩, 数量为5000, 单价为4.8

类结构图

以上示例类的结构图如下所示

image.png

总结

适用场景

对象之间存在一对多的关系,同时一个对象的状态发生改变会影响其他对象。

优点

  • 观察者和被观察者之间是抽象耦合关系,降低了两者的耦合度。
  • 观察者与被观察者之间建立了一套触发机制。

缺点

  • 有可能会出现循环引用。
  • 当观察者对象很多时,发布通知的时间消耗会延长,从而影响程序的效率。

参考