目录
0.背景概述
当今社会互联网的普及极大的提升了人们对外部世界的认知,只要一部手机或者一台电脑,连接上互联网,不出门便可知天下事,但是在上世纪的时候,互联网基本还未出现并且电子设备也还未普及,那些老一辈人了解时事的主要途径基本就是一种-报纸,在电视上面经常看到,每天都会有送报员挨家挨户的送报纸,那么他们怎么知道应该送到哪一家去呢,是因为在送之前,有看报纸这项需求的人士已经事先订阅了他们的报纸,送报员会按照订阅的人的地址去送,咱们今天所要讲解的设计模式的基本原理也与此基本类似。
还是回到订阅报纸这个问题上面,我们来分析以下这类问题的主要特点,这类问题关系到的对象有两类:第一个就是订阅者(需要报纸的人),第二个就是发布者(报社或者送报员),当有人订阅了报社的报纸,而报社有新的报纸出版时就会主动的送到订阅报纸的人手中,那么有一天,订阅者不想看了怎么办,一个电话打过去-取消订阅即可。
在软件设计中,我们也许也会碰到类似的问题,我们想要知道某个对象的时事的状态,当该对象的状态发生改变的时候,我们能够及时的获取改变后的状态来做一写其他的改变,比如天气预报系统,我们想要实时的获取天气情况来更新我们的页面,而又希望能够与状态变化的对象实现松耦合,在设计模式中,观察者模式应运而生。
1.观察者模式简介
1.1 观察者模式的定义
观者者模式:又叫发布-订阅模式,定义了对象之间的一对多依赖。当一个对象的状态发生改变时,他的所有依赖者都能够接收到此通知并且自动更新。
1.2 观察者模式的UML结构图
Subject:抽象主题接口(发布者),用来注册,删除观察者以及当主题的状态发生变更的时候通知所有的已经注册的观察者
ConcreteSubject:具体主题的实现类,实现了抽象主题的所有接口。
Observer:抽象观察者接口,当主题的状态发生变更的时候主要用来做一系列的更新操作的
ConcreteObserver:具体观察者实现类,实现了抽象观察者的的更新接口,做具体的更新操作
2. 观察者模式的java实现
2.1 UML结构图
NewsPaperOffice:报社接口(主题),负责报纸订阅者的注册,撤销,新报纸推送功能。
ConcreteNewsPaperOffice: 报社接口的实现类,实现具体的功能。
Subscribe:报纸订阅者接口
ConcreteSubscribe:订阅者的具体实现类,接收主题的状态更新通知
2.2 用例代码
public interface NewsPaperOffice {
/**
* 添加一个订阅者
* @param subscribe
*/
void registerSubscribe(Subscribe subscribe);
/**
* 撤销一个订阅者
* @param subscribe
*/
void removeSubscribe(Subscribe subscribe);
/**
* 推送最新状态
*/
void notifyUpdate();
}
public class ConcreteNewsPaperOffice implements NewsPaperOffice {
private List<Subscribe> subscribeList = new ArrayList<>();
@Override
public void registerSubscribe(Subscribe subscribe) {
subscribeList.add(subscribe);
}
@Override
public void removeSubscribe(Subscribe subscribe) {
int index = subscribeList.indexOf(subscribe);
if (index >= 0) {
subscribeList.remove(index);
}
}
@Override
public void notifyUpdate() {
subscribeList.forEach(Subscribe::update);
}
public interface Subscribe {
/**
* 订阅者更新操作
*/
void update();
}
public class ConcreteSubscribe implements Subscribe {
private NewsPaperOffice newsPaperOffice;
public ConcreteSubscribe(NewsPaperOffice newsPaperOffice){
this.newsPaperOffice=newsPaperOffice;
System.out.println("你好,我是张三,我要订报纸");
this.newsPaperOffice.registerSubscribe(this);
}
@Override
public void update() {
System.out.println("张三:新报纸来了,关注国家大事,人人有责");
}
}
public class ConcreteSubscribeA implements Subscribe {
private NewsPaperOffice newsPaperOffice;
public ConcreteSubscribeA(NewsPaperOffice newsPaperOffice){
this.newsPaperOffice=newsPaperOffice;
System.out.println("你好,我是李四,我要订报纸");
this.newsPaperOffice.registerSubscribe(this);
}
@Override
public void update() {
System.out.println("李四:报纸到了,看看新闻");
}
}
测试代码以及测试结果如下:
public class ObserverPatternTest {
public static void main(String[] args) {
NewsPaperOffice newsPaperOffice=new ConcreteNewsPaperOffice();
Subscribe subscribe=new ConcreteSubscribe(newsPaperOffice);
Subscribe subscribe1=new ConcreteSubscribeA(newsPaperOffice);
//报纸送给张三和李四
newsPaperOffice.notifyUpdate();
System.out.println("你好,我是张三,我不想订报纸了,麻烦取消下");
//撤销张三的订阅
newsPaperOffice.removeSubscribe(subscribe);
//报纸只给李四派送吧
newsPaperOffice.notifyUpdate();
}
}
3. JDK对观察者模式的支持
在JDK中提供了对观察者模式的原生支持,位于java.util包下面的Observable类以及Observer接口(从java9开始,对于观察者模式的支持已经被废弃,在此,只是演示功能以及结构,本文演示代码的JDK版本是Java10)
3.1结构图
Observable:被观察者(主题,相当于上文的Subject接口),该类的结构中与上文提到的Subject大致类似,主要的有增加观察者,删除观察者,通知状态的方法,只是在功能上面更加的丰富。
obs:私有成员变量,定义了观察者的集合。
changed:私有成员变量,定义了主题的状态信息,默认为false。
addObserver:公共方法,新增一个观察者对象。
deleteObserver(Observer):公共方法,删除一个指定的观察者对象。
deleteObservers():公共方法,被调用时,默认删除所有的观察者对象。
notifyObservers():公共方法,通知所有已经注册的观察者此时自己的状态信息。
notifyObservers(Object):公共方法,与方法notifyObservers()相比,多了一个Object参数,意味着主题可以将任意类型的数 据push给所有观察者对象。
setChanged():protected方法,改变主题的状态标志。
clearChanged():protected方法,还原主题的状态标志为false。
hasChanged():公共方法,判断主题的状态是否发生了变更。
countObservers():公共方法,返回已经注册的观察者的size。
在再用notifyObaservers()或者notifyObservers(Object)方法时,方法体里面判断了此时主题的状态,如果changed为false,则会return,所以在触发通知之前先要调用setChanged()方法,更改主题的状态为true(已经改变)
Observer:观察者接口,只有一个update(Observable,Object)方法,两个参数,第一个接收的是哪一个主题,第二个Object类型参数对应的是Observable类中的notifyObservers(Object)的参数,如果调用的是notifyObservers()方法,则此参数为null。
3.2 JDK内置观察者模式的用例实现
3.2.1 UML结构图
3.2.2 用例代码
public class ConcreteNewsPaperOffice extends Observable {
private String info;
public ConcreteNewsPaperOffice() {
super();
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
//更改状态
setChanged();
//通知修改
notifyObservers();
}
}
ConcreteNewsPaperOffice 继承了Observationvable类,在更改信息的时候首先调用了setChanged(),方法,接着调用notifyObservers()方法通知了所有的观察者(为什么首先调用setChanged()方法,感兴趣的可以看下notifyObservers()方法的源码)。
public class ConcreteSubscribe implements Observer {
private Observable observable;
public ConcreteSubscribe(Observable observable){
this.observable=observable;
System.out.println("你好,我是张三,我要订报纸");
this.observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
System.out.println(((ConcreteNewsPaperOffice)o).getInfo()+",报纸来了,关注下国家大事");
}
}
public class ConcreteSubscribeA implements Observer {
private Observable observable;
public ConcreteSubscribeA(Observable observable){
this.observable=observable;
System.out.println("你好,我是李四,我要订报纸");
this.observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
System.out.println(((ConcreteNewsPaperOffice)o).getInfo()+",看下新闻");
}
}
ConcreteSubscribe 与ConcreteSubscribeA实现了Observer接口,重写了update()方法。
以下是测试代码以及运行结果:
public class ObserverPatternTest {
public static void main(String[] args) {
Observable observable=new ConcreteNewsPaperOffice();
Observer observer=new ConcreteSubscribe(observable);
Observer observer1=new ConcreteSubscribeA(observable);
//报纸送给张三和李四
((ConcreteNewsPaperOffice) observable).setInfo("您好,报纸到了");
System.out.println("你好,我是张三,我不想订报纸了,麻烦取消下");
//撤销张三的订阅
observable.deleteObserver(observer);
//报纸只给李四派送吧
((ConcreteNewsPaperOffice) observable).setInfo("您好,报纸到了");
}
}
3.3 JDK内置观察者模式分析
优点:实现了观察者模式的主要方法,只需要继承Observable类以及Observer接口,便可以实现一个简单的观察者模式,满足业务的需求,同时在Observable类中的主要方法都是用synchronized修饰符来修饰了,在多线程环境中,确保了线程安全性。
缺点:通过分析JDK中Observable的源码,我们知道,Observable是一个类而不是一个接口,而基于Java的特性,在Java环境中,是不允许一个子类有多个父类了,限制了子类的可扩展性
4 JDK源码对观察者模式的运用
在基于C/S架构的系统中,很多是基于很老的Swing模式来设计的,swing中的按钮设计也是典型的观察者模式的应用,在类JButton的父类AbstractButton中有addChangeListener(ChangeListener l),removeChangeListener(ChangeListener l)两个方法,用来添加和删除按钮的事件监听,当button发生改变时,会通知添加的监听者(观察者)。
5 观察者模式的特点
优点:观察者模式体现了软件设计中松耦合的设计原则,也符合了开闭原则的特点-对修改关闭,对扩展开放
缺点:如果对于一个主题而言,有N多个观察者需要关注他的状态,那么当主题的状态发生变更的时候,需要将变化推送给所有已经添加的观察者,可能会有很大的通信开销,再有就是在观察者模式的UML结构图我们可以看到,因为主题需要通知观察者自己的状态,因此持有对观察者的依赖,而观察者需要将自己添加到关注的主题当中,也持有对主题的依赖,产生了循环依赖,在系统设计的特别复杂的时候,可能会造成额外的系统问题。
6 结语
本文简单的讲解了观察者模式的原理,应用以及简单的demo演示,在实际的业务中应该根据自己业务的需要来使用,所有的生搬硬套都可能造成设计的更复杂,模式存在的作用是使得代码更易读,更好的扩展而不是更复杂,冗余
更多内容可以关注公众号:天天coding 一起讨论