设计模式(二):观察者模式
一、概述
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。观察者模式有时成为发布/订阅模式,就是让多个对象在一个对象的状态改变时被通知到。
二、解决问题
当一个系统有多个类协同工作,如果在一个类中需要知道另外一个类的实现细节才能让系统运转,就会导致系统耦合过紧,以后相互依赖的类改变了或者新增了依赖的类,很多类需要同时更改。为了让交互对象之间能够松耦合,我们可以使用观察者模式。
三、结构类图
四、成员角色
抽象主题(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内置的观察者模式实现银行账号资金变动下发各种通知。
//首先我们创建被观察者
import java.util.Observable; 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客户端观察者
import java.util.Observable; import java.util.Observer; 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()); } } }
//邮件观察者
import java.util.Observable; import java.util.Observer; 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()); } } }
//短信观察者
import java.util.Observable; import java.util.Observer; 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自带观察者模式
import java.util.Observer; 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、简单理解就是一个对象状态的改变要通知到其它对象。