设计模式(二)观察者模式
一、模式动机
建立对象之间的一对多关系,一个对象发生改变时会自动通知其他对象,其他对象将做出相应的反应。其中,发生改变的对象叫做主题,被通知的对象叫做观察者。在使用过程中可以根据需要增加和删除观察者,这样系统扩展就变得非常容易。
二、模式定义
在对象之间定义一对多的依赖,当一个对象改变时,依赖它的对象会收到通知,并自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
三、模式结构
观察者模式包含以下4个角色:
1、Subject:抽象主题角色,包含了所有观察者的引用(比如可以保存在ArrayList中),通知观察者以及添加、删除观察者的抽象方法;
2、ConcreteSubject:具体主题角色,继承自Subject,实现了其中的抽象方法;
3、Observer:抽象观察者角色,包含了一个更新自身状态的抽象方法,在获知主题更新后,将调用此方法;
4、ConcreteObserver:具体观察者角色,继承自Observer,实现了更新自身状态的抽象方法。如果有必要,则还可以保存主题的引用。
四、代码实现
抽象主题类Subject:
import java.util.ArrayList; import java.util.List; public abstract class Subject { private List<Observer> observerList = new ArrayList<>(); /** * 添加观察者 * @param observer */ public void attach(Observer observer){ observerList.add(observer); } /** * 删除观察者 * @param observer */ public void detach(Observer observer){ observerList.add(observer); } /** * 通知观察者 * @param newState */ public void notifyObservers(String newState){ for(Observer observer:observerList){ observer.update(newState); } } }
具体主题类ConcreteSubject:
public class ConcreteSubject extends Subject { private String subjectState; public void setNewState(String newState){ this.subjectState = newState; System.out.println("主题状态发生改变,新的状态为:" + newState); this.notifyObservers(newState); } }
抽象观察者接口Observer:
public interface Observer { /** * 观察者更新自身状态 * @param newState 新的状态 */ public void update(String newState); }
具体观察者类ConcreteObserverA:
public class ConcreteObserverA implements Observer { private String oberverState; @Override public void update(String newState) { oberverState = newState; System.out.println("观察者A状态已改变,新的状态为:" + newState); } }
具体观察者类ConcreteObserverB:
public class ConcreteObserverB implements Observer{ private String observerState; @Override public void update(String newState) { observerState = newState; System.out.println("观察者B状态已改变,新的状态为:" + newState); } }
在客户端类Client中进行测试:
public class Client { public static void main(String[] args){ ConcreteSubject subject = new ConcreteSubject(); Observer observerA = new ConcreteObserverA(); subject.attach(observerA); Observer observerB = new ConcreteObserverB(); subject.attach(observerB); // 主题状态改变 subject.setNewState("new state"); } }
运行结果:
主题状态发生改变,新的状态为:new state
观察者A状态已改变,新的状态为:new state
观察者B状态已改变,新的状态为:new state
五、观察者模式中的推和拉
主题的状态中有时会包含很多信息,在上面的例子中,我们把主题中的所有信息都推给了观察者,但观察者可能只需要这些信息中的一部分,实现方法是在主题中将要推送给观察者的信息字段上添加getter方法,然后在观察者更新状态的方法中传入主题的引用(推方式中传入的是主题所有的数据),并通过主题的getter方法来拉取所需要的数据。
用观察者拉取的方式重写上面的代码:
抽象主题类:
import java.util.ArrayList; import java.util.List; public abstract class Subject { private List<Observer> observerList = new ArrayList<>(); /** * 添加观察者 * @param observer */ public void attach(Observer observer){ observerList.add(observer); } /** * 删除观察者 * @param observer */ public void detach(Observer observer){ observerList.add(observer); } /** * 通知观察者 * @param subject 主题引用 */ public void notifyObservers(Subject subject){ for(Observer observer:observerList){ observer.update(this); } } }
具体主题类ConcreteSubject:
public class ConcreteSubject extends Subject { private String subjectState; public void setNewState(String newState){ this.subjectState = newState; System.out.println("主题状态发生改变,新的状态为:" + newState); this.notifyObservers(this); } public String getState(){ return subjectState; } }
观察者接口:
public interface Observer { /** * 观察者更新自身状态 * @param subject 主题引用 */ public void update(Subject subject); }
具体观察者类ConcreteObserverA:
public class ConcreteObserverA implements Observer { private String oberverState; @Override public void update(Subject subject) { oberverState = ((ConcreteSubject)subject).getState(); System.out.println("观察者A状态已改变,新的状态为:" + oberverState); } }
具体观察者类ConcreteObserverB:
public class ConcreteObserverB implements Observer{ private String observerState; @Override public void update(Subject subject) { observerState = ((ConcreteSubject)subject).getState(); System.out.println("观察者B状态已改变,新的状态为:" + observerState); } }
在客户端Client中进行测试:
public class Client { public static void main(String[] args){ ConcreteSubject subject = new ConcreteSubject(); Observer observerA = new ConcreteObserverA(); subject.attach(observerA); Observer observerB = new ConcreteObserverB(); subject.attach(observerB); // 主题状态改变 subject.setNewState("new state"); } }
结果:
主题状态发生改变,新的状态为:new state
观察者A状态已改变,新的状态为:new state
观察者B状态已改变,新的状态为:new state
六、使用JDK内置的观察者模式
在java.util包中,有一个Obsever接口和一个Observable类,分别对应上文中的Observer接口和Subject类,实现了Observer接口的类就是观察者,继承了Observable类的类就是主题(被观察者),这样对主题来讲,使用addObserver(Observer)方法来添加观察者,使用deleteObserver(Observer)方法来删除观察者,当主题发生改变时,使用以下步骤通知观察者:
1、调用setChanged()方法,标记状态已经改变的事实;
2、调用方法notifyObservers()或者notifyObservers(Object object)来通知观察者,前一个方法适用于观察者拉取数据,后一种方法可以在通知的时候,传递任何的数据对象给观察者,主题将数据推给了观察者。
当观察者发现主题发生改变时,则调用自身的update(Observable o, Object arg)来更新自身状态,其中前一个参数标识了观察者所观察的主题,后一个则是主题调用notifyObservers方法时传入的数据对象,如果没有则为空。
代码实现:
主题类(被观察者类):
import java.util.Observable; public class ConcreteSubject extends Observable { private String subjectState; /** * 主题(被观察者)状态改变 * @param newState */ public void setState(String newState){ subjectState = newState; System.out.println("主题状态改变,新的状态是:" + subjectState); System.out.println("通知观察者"); setChanged(); notifyObservers(); } /** * 让观察者拉取数据 * @return */ public String getSubjectState(){ return subjectState; } }
观察者类:
import java.util.Observable; import java.util.Observer; public class ConcreteObserver implements Observer { private String observerState; public ConcreteObserver(Observable observable){ observable.addObserver(this); } @Override public void update(Observable o, Object arg) { observerState = ((ConcreteSubject)o).getSubjectState(); System.out.println("观察者状态改变,新的状态是:" + observerState); } }
使用客户端Client来测试:
public class Client { public static void main(String[] args){ ConcreteSubject subject = new ConcreteSubject(); ConcreteObserver observer = new ConcreteObserver(subject); subject.setState("new state"); } }
结果如下:
主题状态改变,新的状态是:new state
通知观察者
观察者状态改变,新的状态是:new state
在JDK中,还有其他应用体现了观察者模式,比如在一个按钮上添加监听器(ActionListener),当按钮被按下时(主题发生改变),触发ActionListener中的相应逻辑。
六、观察者模式的优缺点
优点:
1、在观察者模式下,主题和观察者之间实现了松耦合。主题只知道观察者实现了一个接口(Observer接口),主题并不需要关心观察者具体的类是谁,做了些什么等其他细节,添加或删除观察者,主题不会受到影响,我们可以独立地复用主题和观察者,而且系统也变得很有弹性,易于扩展。
缺点:
1、当观察者数目较多时,主题可能要花费较长时间通知观察者;
2、观察者之间有循环依赖的话,被观察者会触发他们之间的循环调用,从而导致系统崩溃。
七、适用场景
1、一个对象的改变将导致其他对象发生改变,而不需要关心有多少对象需要跟着改变;
2、一个对象需要通知其他对象,但不需要知道这些对象的具体情况;
3、可以使用观察者模式实现链式触发机制,当对象A发生改变,则影响B对象,B对象的改变又会影响C对象。