观察者模式(Observer Pattern)
定义
观察者模式定义了对象之间的一对多的依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
通俗的定义:发布订阅模式(报社-读者)
模式总结过程
公司经营电商系统,有自己的工厂,当工厂有新产品产出时,通知仓库更新库存、通知商店拿货、通知客户购买
- 初步的实现如下
// 自家工厂
public class Factory {
public void produce(String goods) {
System.out.println("工厂生产了新产品: " + goods);
// 通知库存更新
new Repertory().updateData(goods);
// 通知商店拿货
new Store().sale(goods);
// 通知客户购买
new Customer().buy(goods);
}
}
// 仓库
public class Repertory {
public void updateData(String goods) {
System.out.println("仓库更新库存: " + goods);
}
}
// 商店
public class Store {
public void sale(String goods) {
System.out.println("商店拿货销售: " + goods);
}
}
// 客户
public class Customer {
public void buy(String goods) {
System.out.println("客户购买: " + goods);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Factory factory = new Factory();
factory.produce("智能机器人");
}
}
/*
工厂生产了新产品: 智能机器人
仓库更新库存: 智能机器人
商店拿货销售: 智能机器人
客户购买: 智能机器人
*/
这样的设计,有几个问题
- 针对实现编程,而非针对接口编程
- 如果工厂有新产品后的通知目标增加或减少,需要对原代码进行修改
- 无法在运行时动态地增加或删除通知目标,如我们不想通知客户了
- 耦合程度很高
- 针对上述问题,我们进行优化,工厂类抽象一个接口出来,所有目标也抽象一个接口出来,然后工厂类持有观察者接口的引用
// 主题接口(消息发布者)
public interface Subject {
// 注册观察者,也就是加入需要通知的目标
void registerObserver(Observer observer);
// 移除观察者,也就是删除不需要通知的目标
void removeObserver(Observer observer);
// 通知所有观察者,也就是通知所有的目标
void notifyObservers();
// 生产
void produce(String goods);
}
// 自家工厂
public class Factory implements Subject {
// 观察者集合
private List<Observer> list;
// 产品
private String goods;
public Factory() {
list = new ArrayList<>();
}
// 生产产品
@Override
public void produce(String goods) {
System.out.println("工厂生产了新产品: " + goods);
this.goods = goods;
notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
list.add(observer);
}
@Override
public void removeObserver(Observer observer) {
list.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : list) {
observer.update(goods);
}
}
}
// 抽象观察者(这里自己定义,而非使用jdk源码中的Observer)
public interface Observer {
// 统一一个方法让工厂类调用
void update(String goods);
}
// 仓库
public class Repertory implements Observer {
public void updateData(String goods) {
System.out.println("仓库更新库存: " + goods);
}
@Override
public void update(String goods) {
updateData(goods);
}
}
// 商店
public class Store implements Observer {
public void sale(String goods) {
System.out.println("商店拿货销售: " + goods);
}
@Override
public void update(String goods) {
sale(goods);
}
}
// 客户
public class Customer implements Observer {
public void buy(String goods) {
System.out.println("客户购买: " + goods);
}
@Override
public void update(String goods) {
buy(goods);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Subject subject = new Factory();//工厂
Repertory repertory = new Repertory();//仓库
Store store = new Store();//商店
Customer customer = new Customer();//客户
subject.registerObserver(repertory);//添加仓库为通知目标
subject.registerObserver(store);//添加商店为通知目标
subject.registerObserver(customer);//添加客户为通知目标
subject.produce("智能机器人");
// 客户不想收到通知
subject.removeObserver(customer);
System.out.println("--------");
subject.produce("智能家具");
}
}
/*
工厂生产了新产品: 智能机器人
仓库更新库存: 智能机器人
商店拿货销售: 智能机器人
客户购买: 智能机器人
--------
工厂生产了新产品: 智能家具
仓库更新库存: 智能家具
商店拿货销售: 智能家具
*/
再发一次初步实现的问题
- 针对实现编程,而非针对接口编程
- 如果工厂有新产品后的通知目标增加或减少,需要对原代码进行修改
- 无法在运行时动态地增加或删除通知目标,如我们不想通知客户了
- 耦合程度很高
现在,我们的实现就很不错了,针对初步现实的问题,我们再来对比一下
- 我们针对接口编程,并非针对实现编程
- 通知目标增加时,只需要再实现Observer接口,再把这个实现registerObserver到工厂即可;减少时,工厂类调用removeObserver()即可
- 运行时,也可以动态地增加或删除通知目标(registerObserver/removeObserver)
- 耦合程度很低(工厂类只依赖观察者接口的集合,不需要知道具体观察者的实现)
优点
- 解耦,符合开闭原则
- 符合好莱坞原则:别来找我,我找你们。也就是被观察者通知所有观察者
缺点
- 运行效率较低,一个被观察者,多个观察者时,开发代码和调试会比较复杂
- 消息的通知是默认顺序执行的,若其中一个观察者卡壳,会影响到此观察者后面的观察者执行,影响整体的执行, 多级触发时的效率更让人担忧
应用
- 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变
- 分布订阅型场景
jdk中的观察者模式
- 抽象观察者接口
package java.util;
/**
* A class can implement the <code>Observer</code> interface when it
* wants to be informed of changes in observable objects.
*
* @author Chris Warth
* @see java.util.Observable
* @since JDK1.0
*/
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}
- 主题类(对比工厂类)
package java.util;
public class Observable {
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
// ...
// 只列一小部分方法
}
注意:这里是一个类,虽然实现了大部分功能,但并不是接口
使用jdk API实现上述例子
// 自家工厂
// 继承jdk中的Observable类
public class Factory extends Observable {
public void produce(String goods) {
System.out.println("工厂生产了新产品: " + goods);
setChanged();//只有状态改变才会通知观察者
notifyObservers(goods);
}
}
// 仓库
// 实现jdk中的Obserer接口
public class Repertory implements Observer {
private void updateData(String goods) {
System.out.println("仓库更新库存: " + goods);
}
@Override
public void update(Observable o, Object arg) {
updateData((String) arg);
}
}
// 商店
// 实现jdk中的Obserer接口
public class Store implements Observer {
private void sale(String goods) {
System.out.println("商店拿货销售: " + goods);
}
@Override
public void update(Observable o, Object arg) {
sale((String) arg);
}
}
// 客户
// 实现jdk中的Obserer接口
public class Customer implements Observer {
private void buy(String goods) {
System.out.println("客户购买: " + goods);
}
@Override
public void update(Observable o, Object arg) {
buy((String) arg);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Factory factory = new Factory();//工厂
Repertory repertory = new Repertory();//仓库
Store store = new Store();//商店
Customer customer = new Customer();//客户
factory.addObserver(repertory);//添加仓库为通知目标
factory.addObserver(store);//添加商店为通知目标
factory.addObserver(customer);//添加客户为通知目标
factory.produce("智能机器人");
// 客户不想收到通知
factory.deleteObserver(customer);
System.out.println("--------");
factory.produce("智能家具");
}
}
/*
工厂生产了新产品: 智能机器人
客户购买: 智能机器人
商店拿货销售: 智能机器人
仓库更新库存: 智能机器人
--------
工厂生产了新产品: 智能家具
商店拿货销售: 智能家具
仓库更新库存: 智能家具
*/
优点:省事,被观察者的许多功能已经实现,直接使用即可,也提供了观察者接口
缺点:
- 被观察者通知观察者时,是倒序通知,这个要注意
- 被观察者是一个类,提供方便的同时,也限制了工厂类的扩展