一篇文章带您搞懂观察者模式
定义
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有的观察者对象,使他们能够自动地更新自己。
类图表示
参与者
根据依赖倒置原则可知,我们希望模块与模块之间不是直接耦合到一起,而是依赖于抽象,所以观察者模式抽象出了Subject和Observer。这里的参与者分为4种对象:
1. Subject(主题或者叫通知者):抽象主题,状态发生改变时,会通知所有与之关联的对象。同时提供操作关联对象的方法attach、datach(增加和删除)
2. Observer(观察者):抽象的观察者接口,为所有的具体观察者定义接口,在主题发生改变的时候更新自己
3. ConcreteSubject(具体主题):主题的具体实现,管理观察者对象,在主题发生改变的时候,通知观察者
4. ConcreteObserver(具体观察者):观察者的具体实现
通过参与者,我们可以看出这样设计的好处,无论是具体主题还是具体观察者,都不会直接调用对方。而是调用统一的接口,这样维护、扩展和重用都比较方便,并且由于耦合的双方都只依赖于抽象,而不依赖于具体,所以无论对于具体主题还是具体观察者它们都不需要知道彼此的具体实现,各自的变化都不会影响到另一边。这也符合迪米特原则。(但是抽象主题和抽象观察者还是彼此依赖的)
实例
这里给出的实例源于《大话设计模式》中观察者模式一章。背景是在一家公司中,老板会经常不在公司,所以球迷小张和股迷小赵就贿赂了前台的小姐姐,每次老板到公司视察的时候,前台小姐姐就会通知小张和小赵老板来了,它们就会放下手中的事情,继续工作。
这里的前台小姐姐,就是具体主题,状态就是老板来了,小张和小赵就是具体观察者,分析出他们之间的关系,接下来我们就可以写出观察者模式的代码了。
代码实现:
/** * 主题. * * @author jialin.li * @date 2019-12-26 14:53 */ public interface Subject { /** * 增加一个观察者 */ void attach(Observer observer); /** * 删除一个观察者 */ void detach(Observer observer); /** * 通知 */ void notifyObserver(); /** * 表示当前状态 */ String getStatus(); void setStatus(String status); }
/** * 观察者 * * @author jialin.li * @date 2019-12-26 14:54 */ public abstract class Observer { String name; Subject subject; public Observer(String name, Subject subject) { this.name = name; this.subject = subject; } public abstract void update(); }
import java.util.ArrayList; import java.util.List; /** * 前台. * * @author jialin.li * @date 2019-12-26 15:01 */ public class Receptionist implements Subject { private String status; private List<Observer> observers = new ArrayList<>(); @Override public void attach(Observer observer) { observers.add(observer); } @Override public void detach(Observer observer) { observers.remove(observer); } @Override public void notifyObserver() { for (Observer observer : observers) { observer.update(); } } @Override public String getStatus() { return status; } @Override public void setStatus(String status) { this.status = status; } }
/** * 球迷. * * @author jialin.li * @date 2019-12-26 15:05 */ public class NBAObserver extends Observer{ public NBAObserver(String name, Subject subject) { super(name, subject); } @Override public void update() { System.out.println(name+": 关闭了NBA球赛,继续工作"); } }
/** * 股迷 * * @author jialin.li * @date 2019-12-26 15:04 */ public class StockObserver extends Observer{ public StockObserver(String name, Subject subject) { super(name, subject); } @Override public void update() { System.out.println(name+": 关闭了股票行情,继续工作"); } }
/** * 测试方法 * * @author jialin.li * @date 2019-12-26 14:53 */ public class Main { public static void main(String[] args) { Receptionist xiaojiejie = new Receptionist(); NBAObserver xiaozhang = new NBAObserver("小张", xiaojiejie); StockObserver xiaozhao = new StockObserver("小赵", xiaojiejie); xiaojiejie.attach(xiaozhang); xiaojiejie.attach(xiaozhao); xiaojiejie.setStatus("老板来了"); System.out.println(xiaojiejie.getStatus()); xiaojiejie.notifyObserver(); } }
一个观察者模式的就编写完成了,运行一下吧
老板来了
小张: 关闭了NBA球赛,继续工作
小赵: 关闭了股票行情,继续工作
观察者模式等于发布-订阅模式?
不等于,在发布-订阅模式中,消息的发送方,叫做发布者(publishers),消息的接收方叫做订阅者(Subscriber),发送者是不会直接向订阅者发送信息的,而是通过一个broker(也有资料叫做eventChannel)进行消息的转发。下图总结了这两个模式的主要差别
总结起来就是:
1.观察者模式中,主题和观察者是可以感受到对方存在的,主题会对观察者进行管理。而在发布-订阅模式中,发布者和订阅者不知道对方的存在,他们通过消息代理进行通讯。
2.观察者模式中,主题和观察者实现了松耦合,而发布-订阅模式,发布者和订阅者是完全解耦合的。
3.观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
4.观察者 模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。
Tomcat中的观察者模式:
Tomcat中的容器分为engine、host、context、wrapper ,容器之间是父子关系,通过组合模式将这些容器组合起来,每个容器都具有统一的生命状态以及生命周期方法。
父容器的init()、start()等方法会调用子容器的方法(组合模式)。int()、start()等方法的调用,是因为其父容器状态变化触发的,所以在Tomcat中组件的生命周期被定义成一个个的状态(LifecycleState),而状态的转变则是一种事件,容器会对每一种的事件设置监听器,在监听器中实现一些逻辑,并且对监听器进行管理(增加或删除),这就是一种典型的观察者模式.
观察者模式相关的方法:
public interface Lifecycle { public LifecycleState getState(); public LifecycleListener[] findLifecycleListeners(); public void addLifecycleListener(LifecycleListener listener); public void removeLifecycleListener(LifecycleListener listener); }