一、概述
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。观察者模式有时成为发布/订阅模式,就是让多个对象在一个对象的状态改变时被通知到。
二、解决问题
当一个系统有多个类协同工作,如果在一个类中需要知道另外一个类的实现细节才能让系统运转,就会导致系统耦合过紧,以后相互依赖的类改变了或者新增了依赖的类,很多类需要同时更改。为了让交互对象之间能够松耦合,我们可以使用观察者模式。
三、结构类图
四、成员角色
抽象主题(Subject):把所有对观察者对象的引用保存到一个集合中,每个抽象主题角色都有任何数量的观察者。抽象主题角色提供一个接口,可以增加和删除观察者。一般用一个抽象类和接口实现。
抽象观察者(Observer):为所有具体观察者定义一个接口,在得到主题的通知时更新自己。
具体主题(ConcreteSubject):继承抽象主题,在具体主题内部状态改变时,给所有登记过的观察者发出通知。
具体观察者(ConcreteObserver):实现抽象观察者所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者可以保存一个指向具体主题的引用。
五、应用实例
下面是银行账号资金变动下发通知的例子,只要账号资金变动,就会同时下发app客户端消息、邮件消息、短信消息通知客户账号资金变动了
主题接口
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
抽象主题
abstract public class AbstractSubject implements Subject{
//可以在这里新增Subject没有的方法,以便子类共用
}
观察者接口
public interface Observer {
public void update(double money);
}
app客户端观察者
public class APPObserver implements Observer{
public void update(double money){
//发送app客户端通知账号资金变动
System.out.println("app客户端提醒:您的账户资金变动:" + money);
}
}
邮件观察者
public class MailObserver implements Observer{
public void update(double money){
//发送邮件通知账号资金变动
System.out.println("邮件提醒:您的账户资金变动:" + money);
}
}
短信观察者
public class SmsObserver implements Observer{
public void update(double money){
//发送短信通知账号资金变动
System.out.println("短信提醒:您的账户资金变动:" + money);
}
}
客户银行账号主题
public class AccountSubject extends AbstractSubject{
//观察者集合
private List<Observer> observers;
//资金变动
private double money;
public AccountSubject(){
//主题实例化时,初始化观察者集合
observers = new ArrayList<Observer>();
}
//账号资金变动时通知观察者
public void notifyObservers() {
if(observers.size() > 0){
for(Observer observer:observers){
observer.update(money);
}
}
}
//注册观察者
public void registerObserver(Observer o) {
observers.add(o);
}
//删除观察者
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i > 0){
observers.remove(i);
}
}
//改变账户资金
public void changeAccount(double money){
System.out.println("账户资金变动:" + money);
this.money = money;
//通知观察者
notifyObservers();
}
}
测试观察者
public class TestObserver {
public static void main(String[] args){
//创建账号主题
AccountSubject subject = new AccountSubject();
//创建观察者
Observer appObserver = new APPObserver();
Observer smsObserver = new SmsObserver();
Observer mailObserver = new MailObserver();
//注册观察者到账号主题
subject.registerObserver(appObserver);
subject.registerObserver(mailObserver);
subject.registerObserver(smsObserver);
subject.changeAccount(7.8);
}
}
测试结果:
其实 Java内置了观察者模式,我们可以直接使用java.util包中的Obserber接口和Observable类实现观察者模式。下面我们把上面的代码稍微修改,用Java内置的观察者模式实现银行账号资金变动下发各种通知。
首先我们创建被观察者
public class AccountSubject extends Observable{
//资金变动数额
private double money;
public AccountSubject(){
}
//改变账户资金
public void changeAccount(double money){
System.out.println("(测试java自带观察者模式)账户资金变动:" + money);
this.money = money;
//被观察者状态改变
this.setChanged();
//java内部实现推拉通知,使用时直接调用notifyObservers(),
//或者notifyObservers(Object o),传递的参数会在观察者类接收到
notifyObservers();
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
app客户端观察者
public class APPObserver implements Observer{
//在观察者类图中我们看到,观察者可以持用被观察者的引用
Observable observale;
public APPObserver(Observable observable){
this.observale = observable;
//直接调用java自带的方法,把观察者注册到被观察者
observable.addObserver(this);
}
//具体观察者实现Observer接口的方法,
//arg1参数就是被观察者的notifyObservers(Object o)传过来的参数
public void update(Observable arg0, Object arg1) {
if(arg0 instanceof AccountSubject){
AccountSubject accountSubject = (AccountSubject)arg0;
//发送app客户端通知账号资金变动
System.out.println("app客户端提醒:您的账户资金变动:" + accountSubject.getMoney());
}
}
}
邮件观察者
public class MailObserver implements Observer{
//在观察者类图中我们看到,观察者可以持用被观察者的引用
Observable observale;
public MailObserver(Observable observable){
this.observale = observable;
//直接调用java自带的方法,把观察者注册到被观察者
observable.addObserver(this);
}
//java的观察者有自带的update方法
public void update(Observable arg0, Object arg1) {
if(arg0 instanceof AccountSubject){
AccountSubject accountSubject = (AccountSubject)arg0;
//发送app客户端通知账号资金变动
System.out.println("邮件提醒:您的账户资金变动:" + + accountSubject.getMoney());
}
}
}
短信观察者
public class SmsObserver implements Observer{
//在观察者类图中我们看到,观察者可以持用被观察者的引用
Observable observale;
public SmsObserver(Observable observable){
this.observale = observable;
//直接调用java自带的方法,把观察者注册到被观察者
observable.addObserver(this);
}
//java的观察者有自带的update方法
public void update(Observable arg0, Object arg1) {
if(arg0 instanceof AccountSubject){
AccountSubject accountSubject = (AccountSubject)arg0;
//发送app客户端通知账号资金变动
System.out.println("短信提醒:您的账户资金变动:" + + accountSubject.getMoney());
}
}
}
测试java自带观察者模式
public class TestObserver {
public static void main(String[] args){
//创建被观察者,也就是账号主题
AccountSubject observble = new AccountSubject();
//创建观察者
Observer appObserver = new APPObserver(observble);
Observer smsObserver = new SmsObserver(observble);
Observer mailObserver = new MailObserver(observble);
//账号资金变动
observble.changeAccount(7.8);
}
}
测试结果:
六、优点与缺点
1.优点
(1)、观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖抽象,而不是依赖具体。从而使各自的变化都不会影响到另一方。主题并不知道任何一个具体的观察者,只知道他们有一个共同的接口。
(2)、观察者模式支持“广播通信”。主题会向所有的观察者发出通知。
(3)、观察者模式符合“对修改关闭,对扩展开放”的设计原则。
2.缺点
(1)、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会话费很多时间。
(2)、观察者只知道被观察者发生了变化,但并不知道被观察者是如何发生变化的。
七、使用场景
1、一个抽象模型有两个方面,其中一个方面依赖另一个方面。将这些方面封装在独立的对象中使它们可以各自独立的改变和复用。
2、一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少个对象将发生改变,可以降低对象之间的耦合度。
3、一个对象必须通知其他对象,但而不知道这些对象是谁。就是对象之间的松耦合
八、总结
1、观察者模式定义了对象之间一对多关系。
2、有多个观察者时,不可以依赖特定的通知次序。
3、java有通用的观察者模式(java.util.Observable)。
4、观察者与被观察者之间用松耦合方式结合。
5、简单理解就是一个对象状态的改变要通知到其它对象。