观察者模式
微信公众号:新时代程序猿
关注可了解更多的JAVA,PYTHON,ANDROID教程及开发技术。
问题或建议,请公众号留言;
如果你觉得文章对你有帮助,欢迎赞赏
什么是观察者模式
在许多设计中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化,也就是说当对象间存在一对多关系时,在这样的情况下就可以使用观察者模式。当一个对象被修改时,则会自动通知它的依赖对象。
通俗地说,就像我们微信的公众号,比如我喜欢看编程类的,那么我就订阅了“新时代程序员”,当菜鸟阿庆发布文章后,所有关注过的都会收到新消息。“新时代程序员”是被观察者,粉丝们是观察者,这就是典型的观察者模式,怎么样?是不是很好理解。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
观察者模式的结构中包含四种角色:
(1)主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及通知观察者更新数据的方法。
(2)观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。
(3)具体主题(ConcreteSubject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者。
(4)具体观察者(ConcreteObserver):具体观察者是实现观察者接口类的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者。
UML类图
Subject也就是被观察者拥有所有观察者的引用,提供一个注册观察者与删除观察者的方法以及通知所有观察者更新的方法。Observer是观察者接口,为所有观察者定义了一个方法,用于主题改变时更新自己。具体观察者各自实现Observer接口方法,可以及时得到通知并更新。
关键概念理解
观察者设计模式中主要区分两个概念:
- 观察者:指观察者对象,也就是消息的订阅者;
- 被观察者:指要观察的目标对象,也就是消息的发布者。
通知观察者的方式
当被被观察者数据发生变化时,主要通过两种方式通知观察者,具体如下:
- 推:消息以类似广播的形式通知观察者,观察者只能被动、无条件接受;
- 拉:接收到被观察者的通知,可以自主决定获取消息。
观察者模式的实现
下面通过两种方式实现观察者设计模式的实现,具体如下:
下面我们看两个例子:
- 资金手写观察者设计模式
- 使用Java API 提供的观察者设计模式, Observer 和 Observeable 实现观察者模式
手写观察者模式
首先创建被观察者,具体如下:
/**
* 观察者要观察的目标对象
* @author wyq
*/
public abstract class Subject {
protected ArrayList<Observer> observerList = new ArrayList<>();
//表示观察者对目标对象(被观察者)开始观察
public void registerObserver(Observer obs) {
observerList.add(obs);
}
//表示取消某观察者对目标对象(被观察者)的观察
public void unRegisterObserver(Observer obs) {
observerList.remove(obs);
}
//当目标对象(被观察者)的状态发生变化时,及时更新观察者的状态
public void notifyAllObserver(){
for (Observer observer : observerList) {
observer.update(this);
}
}
}
创建具体的被观察者,具体如下:
/**
* 具体的目标对象(被观察者)
* @author wyq
*/
public class ConcreteSubject extends Subject{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
//当数据发生变化时通知其他的观察者
notifyAllObserver();
}
}
然后,为了统一方便,定义观察者接口,具体如下:
/**
* 具体的观察者
* @author wyq
*/
public class ConcreteObserver implements Observer{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
@Override
public void update(Subject subject) {
//获取目标对象的数据变化,同时更新当前观察者
ConcreteSubject concreteSubject = (ConcreteSubject)subject;
state = concreteSubject.getState();
}
}
最后,测试观察者设计模式,具体如下:
/**
* Main
* @author wyq
*/
public class Client {
public static void main(String[] args) {
//创建具体的目标对象(被观察者)
ConcreteSubject concreteSubject = new ConcreteSubject();
//创建多个具体的观察者
ConcreteObserver obs1 = new ConcreteObserver();
ConcreteObserver obs2 = new ConcreteObserver();
ConcreteObserver obs3 = new ConcreteObserver();
//让观察者观察目标对象(被观察者)的数据变化
concreteSubject.observerList.add(obs1);
concreteSubject.observerList.add(obs2);
concreteSubject.observerList.add(obs3);
//改变某个目标对象(被观察者)的数据
concreteSubject.setState(10);
//观察者数据是否与被观察者数据变化一致
System.out.println("观察者obs1:"+obs1.getState());
System.out.println("观察者obs2:"+obs2.getState());
System.out.println("观察者obs3:"+obs3.getState());
}
}
显然,执行结果肯定如下:
观察者obs1:10
观察者obs2:10
观察者obs3:10
通过对目标对象数据的改变,更新了与之相对应的观察者的数据,实现了消息的订阅和发送。
Java API 提供的观察者设计模式
Java API 提供的观察者设计模式主要通过 Observer 和 Observeable 来实现,首先创建一个类继承 Observeable 作为被观察者,具体如下:
/**
* 被观察者(目标对象)
* @author wyq
*/
public class ConcreteSubject extends Observable{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
//表示数据已经发生变化
setChanged();
//具体的目标对象数据变化的时候通知观察者
notifyObservers(state);
}
}
然后,创建一个类继承 Observer 作为观察者,具体如下:
/**
* 观察者
* @author wyq
*/
public class ConcreteObserver implements Observer{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
@Override
public void update(Observable arg0, Object arg1) {
ConcreteSubject concreteSubject = (ConcreteSubject) arg0;
//根据目标对象(被观察者)的数据变化更新当前观察者的数据
this.state = concreteSubject.getState();
}
}
最后,测试观察者设计模式,具体如下:
/**
* 测试观察者设计模式
* @author wyq
*/
public class Client {
public static void main(String[] args) {
ConcreteSubject concreteSubject = new ConcreteSubject();
ConcreteObserver obs1 = new ConcreteObserver();
ConcreteObserver obs2 = new ConcreteObserver();
ConcreteObserver obs3 = new ConcreteObserver();
concreteSubject.addObserver(obs1);
concreteSubject.addObserver(obs2);
concreteSubject.addObserver(obs3);
concreteSubject.setState(100);
System.out.println("观察者obs1:"+obs1.getState());
System.out.println("观察者obs2:"+obs2.getState());
System.out.println("观察者obs3:"+obs3.getState());
}
}
显然,执行结果肯定如下:
观察者obs1:100
观察者obs2:100
观察者obs3:100
观察者模式的优缺点
优点:观察者与被观察者抽象耦合,可定义一种稳定的消息触发机制。
缺点:如果被观察者有多个间接的观察者,消息的传递将消耗更多时间,如果观察者与被观察者之间循环依赖,最终会导致系统崩溃。
使用场景
(1)当一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合。
(2)当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象需要更新数据。
使用观察者模式也有两个重点问题要解决:
广播链的问题
如果你做过数据库的触发器,你就应该知道有一个触发器链的问题,比如表 A 上写了一个触发器,内容是一个字段更新后更新表 B 的一条数据,而表 B 上也有个触发器,要更新表 C,表 C 也有触发器…,完蛋了,这个数据库基本上就毁掉了!我们的观察者模式也是一样的问题,一个观察者可以有双重身份,即使观察者,也是被观察者,这没什么问题呀,但是链一旦建立,这个逻辑就比较复杂,可维护性非常差,根据经验建议,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),这还是比较好控制的;
异步处理问题
被观察者发生动作了,观察者要做出回应,如果观察者比较多,而且处理时间比较长怎么办?那就用异步呗,异步处理就要考虑线程安全和队列的问题,这个大家有时间看看 Message Queue,就会有更深的了解。
评论区