附链
你也可以在这些平台阅读本文:
定义
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者(观察者)都会收到通知并且自动更新。
观察者模式提供了一种对象设计,让观察者和被观察者(主题)之间松耦合。
四个角色
观察者模式的主要角色有以下四个:
- 抽象主题角色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
类结构图
以上示例类的结构图如下所示
总结
适用场景
对象之间存在一对多的关系,同时一个对象的状态发生改变会影响其他对象。
优点
- 观察者和被观察者之间是抽象耦合关系,降低了两者的耦合度。
- 观察者与被观察者之间建立了一套触发机制。
缺点
- 有可能会出现循环引用。
- 当观察者对象很多时,发布通知的时间消耗会延长,从而影响程序的效率。
参考
- 《Head First 设计模式》
- 《大话设计模式》
- 维基百科-观察者模式
- 菜鸟教程-观察者模式