👉文章示例代码👈

定义

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

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

四个角色

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

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

场景示例

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

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

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

创建具体主题

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @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 表示的是主题中发布通知时传递的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @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()));
}
}

创建缺省实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* @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;
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @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

总结

适用场景

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

优点

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

缺点

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

参考