3、观察者设计模式
观察者设计模式
观察者模式定义
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者可以有多个,但是主题只有一个;
例如当我们在购物网站购物关注某一个商品后,如果商品降价,系统聚会自动给消费者发送消息,我们分析一下购物场景在观察者模式中的实例关系:
- 主题对象:主题对象就是我们关注的商品,商品即主题;
- 观察者:用户就是观察者;
- 观察者注册:用户点击关注后,系统代理用户进行自动关注,即用户可以看作观察者;
- 通知触发条件:当商品降价后,触发通知机制;
观察者模式类图
接下来看下观察者模式中的角色组成:
- 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题对象又叫做抽象被观察者(Observable)角色。
- 具体主题(Subject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
- 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
- 具体观察者(ConcreteObserver)角色:存储与主题的状态自洽的状态。具体观察者角色实现抽象观察者角色所要求的的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
管擦和这模式工作流程
整体上看,观察者模式的工作流程分为以下几个步骤:
- 注册观察者:观察者通过调用主题的注册方法(如 add())将自己注册到主题中。
- 状态改变:主题的状态或数据发生变化(例如,属性值改变)。
- 通知观察者:主题执行通知方法(如 notify()),遍历所有注册的观察者。调用每个观察者的更新方法(如 update()),并传递必要的数据。
- 观察者更新:观察者接收到通知后,根据主题的当前状态进行相应的处理(如更新自己的状态或执行操作)。
Java实现观察者模式
主题接口实现
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
主题是一个接口,主要用来定义观察者的注册,移除,通知等方法,此接口中通常定义一些顶级方法,不易将此接口扩展的太大;
- registerObserver:观察者用来实现注册功能
- removeObserver:从主题中删除观察者功能,删除后观察者不在接收通知;
- notifyObservers:通知所有观察者方法,当主题内容变化后,触发此方法通知所有观察者;
具体主题实现
public class ConcreteSubject implements Subject{
private List<Observer> observers = new ArrayList<>();
private String message;
/**
* 注册一个观察者
* @author yourname
* @date 12:24 2024/12/23
* @param observer
**/
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
/**
* 移除一个观察者
* @author yourname
* @date 12:28 2024/12/23
* @param observer
**/
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
/**
* 通知所有观察者
* @author yourname
* @date 12:28 2024/12/23
**/
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(message);
}
}
/**
* 如果更新主题数据 就通知所有观察者
* @author yourname
* @date 12:38 2024/12/23
* @param message
**/
public void setMessage(String message) {
this.message = message;
notifyObservers();
}
}
具体主题实现主题接口,并且实现其接口中的方法,当有观察者加入到主题当中时,通过调用具体的主题实现方法加入主题;
message:主题中保存数据,当此内容更新后会触发通知所有观察者;
observers:注册的观察者列表;
观察者接口实现
public interface Observer {
void update(String message);
}
观察者接口也是一个顶级接口,定义当具体的观察者收到通知后做出的动作;
具体观察者实现
public class ConcreteObserver implements Observer{
private String name ;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
具体观察者实现观察者接口,当观察者收到通知后,就触发动作调用;
客户端测试
@Slf4j
public class Observer_test_API {
public static void main(String[] args) {
/*创建一个主题*/
ConcreteSubject subject = new ConcreteSubject();
/*创建观察者*/
ConcreteObserver observer1 = new ConcreteObserver("observer1");
ConcreteObserver observer2 = new ConcreteObserver("observer2");
/*注册观察者*/
subject.registerObserver(observer1);
subject.registerObserver(observer2);
/*主题进行数据更新*/
subject.setMessage("update subject data");
subject.setMessage("update subject data2");
}
}
客户端创建一个主题,然后主题中加入两个观察者,当客户端的内容被更新后,就触发调用观察者通知,观察者收到通知后调用自己的update方法;
观察者模式适用场景
观察者模式适用于一种关联行为的触发场景,建立一种消息触发通知模型,消费者订阅的主题内容发生变化后,通知所有消费者;必为微信公众号订阅通知机制,购物网站的通知机制;
优缺点
优点
- 松耦合:观察者模式实现了对象之间的松耦合,观察者和主题之间并不需要明确的依赖关系,只通过接口来交互。这使得系统更具扩展性。并且 观察者 和 被观察者 之间 , 建立了一个 抽象的 耦合 ; 由于 耦合 是抽象的 , 可以很容易 扩展 观察者 和 被观察者 ;
- 动态交互:可以在运行时添加或移除观察者,灵活性强。这对于需要动态调整的程序非常有用。
- 多种订阅方式:不同的观察者可以对同一主题订阅不同的条件,以应对复杂的业务逻辑。
- 广播通信 : 观察者模式 支持 广播通信 , 类似于消息广播 , 如果需要接收消息 , 只需要注册一下即可 ;
缺点
- 通知机制开销:在主题状态改变时,需要通知所有观察者,这可能导致性能问题,尤其是在观察者数量较多时,每次通知观察者需要耗费很长时间;
- 观察者管理复杂:观察者的管理可能会变得复杂,特别是在大量观察者需要注销的情况下,需要确保所有观察者的状态一致性。
- 隐式依赖:虽然观察者模式松耦合,但是观察者和主题之间仍然存在隐式依赖关系,可能导致在系统分析和维护时的复杂性增加。
- 循环调用 : 避免 循环调用 , 观察者 与 被观察者 之间 绝对不允许循环依赖 , 否则会触发 二者 之间的循环调用 , 导致系统崩溃 ;
jdk中的观察者模式
JDK 中提供了观察者模式的支持 ;
被观察者 : 被观察者 继承 Observable 类 ;
观察者 : 观察者 实现 Observer 接口 ;
关联 观察者 与 被观察者 : 调用 被观察者 的 addObserver 方法 , 关联二者 ;
触发通知 : 被观察者 数据改变时 , 调用 setChanged 和 notifyObservers 方法 , 会自动回调 观察者的 update 方法 ;