简介
观察者模式(Observer Pattern)属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe
)模式、模型-视图(Model/View
)模式、源-监听器(Source/Listener
)模式或从属者(Dependents
)模式。
观察者模式的推拉方式
- 推方式:
主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
- 拉方式:
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
实例
观察者模式所涉及的角色有:
-
抽象主题角色(
Subject
):抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList
对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable
)角色; -
具体主题角色(
ConcreteSubject
):将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable
)角色; -
抽象观察者角色(
Observer
):为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口; -
具体观察者角色(
ConcreteObserver
):存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
- 推方式:
// 抽象观察者角色类
public interface Observer {
void update(String message);
}
// 抽象主题角色类
public abstract class AbstractSubject {
private List<Observer> list = new ArrayList<>();
public void attach(Observer observer) {
list.add(observer);
}
public void detach(Observer observer) {
list.remove(observer);
}
public void notifyObserver(String message) {
for (Observer observer : list) {
observer.update(message);
}
}
}
// 具体观察者角色类
public class WxUserObserver implements Observer {
private String name;
public WxUserObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "收到推送消息:" + message);
}
}
// 具体主题角色类
public class WxSubject extends AbstractSubject {
public void change(String message) {
this.notifyObserver(message);
}
}
// 客户端
public class Client {
@Test
public void test() {
WxSubject subject = new WxSubject();
final WxUserObserver zs = new WxUserObserver("张三");
final WxUserObserver ls = new WxUserObserver("李四");
final WxUserObserver ww = new WxUserObserver("王五");
// 初始化三个关注用户
subject.attach(zs);
subject.attach(ls);
subject.attach(ww);
subject.change("开始推送消息1");
// 张三取消关注
subject.detach(zs);
subject.change("开始推送消息2");
}
}
复制代码
张三收到推送消息:开始推送消息1
李四收到推送消息:开始推送消息1
王五收到推送消息:开始推送消息1
李四收到推送消息:开始推送消息2
王五收到推送消息:开始推送消息2
复制代码
- 拉方式:
// 抽象观察者角色类
public interface Observer {
void update(AbstractSubject subject);
}
// 抽象主题角色类
public abstract class AbstractSubject {
private List<Observer> list = new ArrayList<>();
public void attach(Observer observer) {
list.add(observer);
}
public void detach(Observer observer) {
list.remove(observer);
}
public void notifyObserver() {
for (Observer observer : list) {
observer.update(this);
}
}
}
// 具体观察者角色类
public class WxUserObserver implements Observer {
private String name;
public WxUserObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "收到推送消息:" + message);
}
}
// 具体主题角色类
public class WxSubject extends AbstractSubject {
private String state;
public String getState() {
return state;
}
public void change(String state) {
this.state = state;
this.notifyObserver();
}
}
// 客户端
public class Client {
@Test
public void test() {
WxSubject subject = new WxSubject();
final WxUserObserver zs = new WxUserObserver("张三");
final WxUserObserver ls = new WxUserObserver("李四");
final WxUserObserver ww = new WxUserObserver("王五");
// 初始化三个关注用户
subject.attach(zs);
subject.attach(ls);
subject.attach(ww);
subject.change("State");
// 李四取消关注
subject.detach(ls);
subject.change("new State");
}
}
复制代码
张三状态更新为:State
李四状态更新为:State
王五状态更新为:State
张三状态更新为:new State
王五状态更新为:new State
复制代码
- JDK 实现:
在Java
语言的java.util
库里面,提供了一个Observable
类以及一个Observer
接口,构成了Java语言对观察者模式的支持。 Observer
接口只定义了一个update()
方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()
方法就会调用这一方法。 Observable
类是被观察者类的基类。java.util.Observable
提供公开的方法支持观察者对象,这些方法中有两个对Observable
的子类非常重要:一个是setChanged()
,另一个是notifyObservers()
。第一方法setChanged()
被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个是notifyObservers()
,这个方法被调用时,会调用所有登记过的观察者对象的update()
方法,使这些观察者对象可以更新自己。
// 观察者
public class WxUserObserver implements Observer {
private String name;
public WxUserObserver(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(name + "状态更新为:" + ((WxObservable) o).getState());
}
}
// 被观察者
public class WxObservable extends Observable {
private String state;
public String getState() {
return state;
}
public void change(String state) {
this.state = state;
this.setChanged(); // 设置一个内部标记变量
this.notifyObservers(); // 调用所有登记过的观察者对象的update()方法
}
}
public class Client {
@Test
public void test() {
WxObservable observable = new WxObservable();
final WxUserObserver zs = new WxUserObserver("张三");
final WxUserObserver ls = new WxUserObserver("李四");
final WxUserObserver ww = new WxUserObserver("王五");
observable.addObserver(zs);
observable.addObserver(ls);
observable.addObserver(ww);
observable.change("State");
observable.deleteObserver(ls);
observable.change("new State");
}
}
复制代码
张三状态更新为:State
李四状态更新为:State
王五状态更新为:State
张三状态更新为:new State
王五状态更新为:new State
复制代码
用JDK提供的这种方式能很好方便的实现,推方式和拉方式的设计模式。
类图
推方式:
拉方式:
JDK实现:
优点
- 观察者和被观察者是抽象耦合的;
- 建立一套触发机制。
缺点
-
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
-
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;
-
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
适用场景
-
一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用;
-
一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度;
-
一个对象必须通知其他对象,而并不知道这些对象是谁;
-
需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
总结
观察者模式用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在 Java
中已经对观察者模式的支持类 java.util.Observer
、java.util.Observable
,不需要我们自己实现。使用中注意避免循环引用。如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。