观察者模式是对象的行为模式。又叫做发布-订阅模式、模型-视图模式、源-监听器模式。
抽象主题角色:主题角色将所有对观察者对象的引用到保存在一个集合里,每个主题都可以拥有任意数量的观察者。抽象主题提供一个接口,可以增加或者删除观察者对象。主题角色又叫被观察者。
具体主题角色:将有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色是抽象主题的一个具体子类实现。
抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知的时候更新自己。
具体观察者角色:抽象观察者的具体子类实现。
代码
抽象主题
package com.demo.exercise.observer; /** * 抽象主题 */ public interface Subject { void add(Observer observer); void del(Observer observer); void notifyObservers(); }
抽象观察者
package com.demo.exercise.observer; /** * 抽象观察者 */ public interface Observer { void update(); }
具体观察者
package com.demo.exercise.observer; /** * 具体观察者 */ public class ConcreteObserver implements Observer { @Override public void update() { System.out.println(this + ":接收到通知了..."); } }
具体主题
package com.demo.exercise.observer; import java.util.Iterator; import java.util.Vector; /** * 具体主题 */ public class ConcreteSubject implements Subject { private Vector<Observer> observers = new Vector<>(); @Override public void add(Observer observer) { observers.add(observer); } @Override public void del(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { System.out.println("通知所有观察者..."); Iterator<Observer> iterator = observers.iterator(); while (iterator.hasNext()){ iterator.next().update(); } } }
运行
public static void main(String[] args) { Observer a = new ConcreteObserver(); Observer b = new ConcreteObserver(); Subject subject = new ConcreteSubject(); subject.add(a); subject.add(b); subject.notifyObservers(); }
输出
优化方案
上面的【抽象主题角色】,其中管理集合的方法,都是由子类来实现的,而实际情况则是这些管理集合的方法是所有实现子类共用的,所以可以把这些转移到抽象主题中去。
抽象主题(这里还是用的interface,你也可以用abstract class)
package com.demo.exercise.observer; import java.util.Iterator; import java.util.Vector; /** * 抽象主题 */ public interface Subject { Vector<Observer> observers = new Vector<>(); default void add(Observer observer){ observers.add(observer); } default void del(Observer observer){ observers.remove(observer); } default void notifyObservers(){ System.out.println("通知所有观察者..."); Iterator<Observer> iterator = observers.iterator(); while (iterator.hasNext()){ iterator.next().update(); } } /** * 子类需要自定义的方法 * @param status */ void change(String status); }
具体子类
package com.demo.exercise.observer; /** * 具体主题 */ public class ConcreteSubject implements Subject { private String status; /** * 主题状态改变,调用通知方法 * @param status */ public void change(String status){ System.out.println(status); this.status = status; this.notifyObservers(); } }
运行
public static void main(String[] args) { Observer a = new ConcreteObserver(); Observer b = new ConcreteObserver(); Subject subject = new ConcreteSubject(); subject.add(a); subject.add(b); subject.change("主题状态改变"); }
输出
引申
那么这种设计模式用来解决什么问题呢?类似于下面这种代码,大家初学Java的时候肯定用过,点击按钮事件,只要点击按钮,就会自动执行相关方法。
JButton button = new JButton(); button.addActionListener((event) -> { // TODO });
其中的原理也是观察者模式。下面我就来简单模拟一下
抽象主题
package com.demo.exercise.observer; import java.util.Iterator; import java.util.Vector; /** * 抽象主题 */ public abstract class Button { private Vector<ActionListener> observers = new Vector<>(); void addActionListener(ActionListener observer){ observers.add(observer); } void removeActionListener(ActionListener observer){ observers.remove(observer); } void notifyObservers(){ Iterator<ActionListener> iterator = observers.iterator(); while (iterator.hasNext()){ iterator.next().actionPerformed(); } } }
抽象观察者
package com.demo.exercise.observer; /** * 抽象观察者 */ public interface ActionListener { void actionPerformed(); }
具体主题
package com.demo.exercise.observer; /** * 具体主题 */ public class JButton extends Button { /** * 点击事件 */ public void click(){ System.out.println("【假装产生了点击事件】"); this.notifyObservers(); } }
运行
public static void main(String[] args) { JButton button = new JButton(); button.addActionListener(() -> { System.out.println("点击事件处理..."); }); button.addActionListener(() -> { System.out.println("另外一个点击事件处理..."); }); button.click(); }
输出
Java对观察者模式的支持
一个是被观察者:java.util.Observable
一个是观察者:java.util.Observer
Observer里面只有一个update方法。
public interface Observer { /** * This method is called whenever the observed object is changed. An * application calls an <tt>Observable</tt> object's * <code>notifyObservers</code> method to have all the object's * observers notified of the change. * * @param o the observable object. * @param arg an argument passed to the <code>notifyObservers</code> * method. */ void update(Observable o, Object arg); }
当被观察者的状态发生改变,就会调用这一方法。
public void notifyObservers(Object arg) { /* * a temporary array buffer, used as a snapshot of the state of * current Observers. */ Object[] arrLocal; synchronized (this) { /* We don't want the Observer doing callbacks into * arbitrary code while holding its own Monitor. * The code where we extract each Observable from * the Vector and store the state of the Observer * needs synchronization, but notifying observers * does not (should not). The worst result of any * potential race-condition here is that: * 1) a newly-added Observer will miss a * notification in progress * 2) a recently unregistered Observer will be * wrongly notified when it doesn't care */ if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); }
用法呢,我再给你示范一下。
创建一个事件对象
package com.demo.exercise.observer; /** * 事件对象 */ public class ClickEvent { private String source; public ClickEvent(String source) { this.source = source; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } }
package com.demo.exercise.observer; import java.util.Observable; import java.util.Observer; /** * 具体观察者 */ public class JJButton extends Observable { public void click(){ // 事件对象 ClickEvent clickEvent = new ClickEvent("点击事件"); // changed 置为true,表示状态改变,才会通知所有观察者,与之对应的是clearChanged this.setChanged(); // 通知所有观察者 this.notifyObservers(clickEvent); } public void addListener(Observer o){ this.addObserver(o); } }
运行
public static void main(String[] args) { JJButton button = new JJButton(); button.addListener((o, arg) -> { ClickEvent event = (ClickEvent) arg; System.out.println("事件源:" + event.getSource()); }); button.click(); }
输出