设计模式之观察者模式
观察者模式(Observer)完美地将观察者和被观察的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
实现观察者模式有很多形式,比较直观的一种是使用一种“注册——通知——撤销注册”的形式。下面的三个图详细的描述了这样一种过程:
观察者(Observer)将自己注册到被观察对象(Subject)中,被观察对象将观察者存放在一个容器(Container)里。
被观察的对象发生了某种变化(如图中的SomeChange)时,从容器中得到所有注册过的观察者,将变化通知观察者。
观察者告诉被观察者要撤销观察,被观察者从容器中将观察者去除。
需要注意的是.,观察者将自己注册到被观察者的容器中时,被观察者不应该过问观察者的具体类型,而是应该使用观察者的接口。这样的优点是:假定程序中还有别的观察者,那么只要这个观察者也是相同的接口实现即可。一个被观察者可以对应多个观察者,当被观察者发生变化的时候,他可以将消息一一通知给所有的观察者。基于接口,而不是具体的实现——这一点为程序提供了更大的灵活性。
一般来说,当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。 如何解决:使用面向对象技术,可以将这种依赖关系弱化。 关键代码:在抽象类里有一个 ArrayList 存放观察者们。 应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。 优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。 缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。 使用场景: 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。 一个对象必须通知其他对象,而并不知道这些对象是谁。 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
我们接下来模仿有若干顾客在一个点餐台前在点餐后,等候被告知餐品做好,从而去取餐。这个业务场景通过观察者模式来实现。
package com.itszt; import java.util.HashSet; import java.util.Set; /** * 被观察的抽象主题 */ public abstract class Subject { //1.知道有哪些观察者在观察自己,每来一个观察者就注册进来 private Set<Observer> observers=new HashSet<>(); public void registerObserver(Observer observer){ observers.add(observer); } //2.通知注册进来的所有观察者 public void notifyObservers(Object data){ for(Observer observer:observers){ observer.onNotify(data); } } //3.注销某个观察者 public void removeObserver(Observer observer){ observers.remove(observer); } //4.自己的主要业务 public abstract void doWork(); } -------------------------------------------------------- package com.itszt; /** * 抽象观察者 */ public abstract class Observer { //1.知道去观察谁--传入一个主题对象 private Subject subject; public Observer(Subject subject){ this.subject=subject; subject.registerObserver(this); } //2.响应动作--一旦主题状态发生变化,观察者采取的响应动作 public abstract void onNotify(Object message); } ------------------------------------------------------ package com.itszt; /** * 被观察的具体主题 */ public class 点餐台 extends Subject{ @Override public void doWork() { new Thread(){ @Override public void run() { int num=0; while(true){ try { //假定每做一个餐品需要耗时5秒 Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } num++; System.out.println("第 "+num+" 个披萨做出来了,通知内容如下:"); String message="第"+num+"个披萨已做好,请知悉"; System.out.println(message); System.out.println("---------------------"); notifyObservers(message); } } }.start(); } } ----------------------------------------------------- package com.itszt; /** * 具体的观察者,顾客A */ public class 顾客A extends Observer{ public 顾客A(Subject subject){ super(subject); } @Override public void onNotify(Object message) { System.out.println("我是"+this.getClass().getName()+",已收到您的通知:\n"+message); System.out.println("---------------------"); } } --------------------------------------------------- package com.itszt; /** * 具体的观察者,顾客B */ public class 顾客B extends Observer{ public 顾客B(Subject subject){ super(subject); } @Override public void onNotify(Object message) { System.out.println("我是"+this.getClass().getName()+",已收到您的通知:\n"+message); System.out.println("---------------------"); } } ----------------------------------------------------- package com.itszt; /** * 具体的观察者,顾客C */ public class 顾客C extends Observer{ public 顾客C(Subject subject){ super(subject); } @Override public void onNotify(Object message) { System.out.println("我是"+this.getClass().getName()+",已收到您的通知:\n"+message); System.out.println("---------------------"); } } ------------------------------------------------- package com.itszt; /** * 测试类 */ public class Test { public static void main(String[] args) { Subject subject=new 点餐台(); Observer clientA=new 顾客A(subject); Observer clientB=new 顾客B(subject); Observer clientc=new 顾客C(subject); //假定注销clientc subject.removeObserver(clientc); subject.doWork(); } }
执行上述测试类,代码执行如下:
第 1 个披萨做出来了,通知内容如下: 第1个披萨已做好,请知悉 --------------------- 我是com.itszt.顾客B,已收到您的通知: 第1个披萨已做好,请知悉 --------------------- 我是com.itszt.顾客A,已收到您的通知: 第1个披萨已做好,请知悉 --------------------- 第 2 个披萨做出来了,通知内容如下: 第2个披萨已做好,请知悉 --------------------- 我是com.itszt.顾客B,已收到您的通知: 第2个披萨已做好,请知悉 --------------------- 我是com.itszt.顾客A,已收到您的通知: 第2个披萨已做好,请知悉 --------------------- 第 3 个披萨做出来了,通知内容如下: 第3个披萨已做好,请知悉 --------------------- 我是com.itszt.顾客B,已收到您的通知: 第3个披萨已做好,请知悉 --------------------- 我是com.itszt.顾客A,已收到您的通知: 第3个披萨已做好,请知悉 --------------------- 【注:由于while(true)为死循环,所以会无限地执行下去】