设计模式之观察者模式
关注公众号JavaStorm 获取最新文章。
观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
主要角色
- 主题接口 Subject:管理所有的观察者以及数据变化后通知观察者。
- 观察者接口 Observer:接受自己订阅的主题发布的数据。
- 主题实现类。
- 观察者实现类。
使用场景
-
报社的业务就是出版报纸,客户订阅该报社,那么只要有新的报纸出版就会给订阅报社的客户送来,只要一直是报社的订阅客户,就能一直收到新报纸。
-
当你不想订阅,取消就可以,就不会再收到通知。
-
报社提供订阅与取消订阅的入口。
实际上这里就是一个观察者模式的例子,报社充当 Subject 主题角色,订阅报社的客户就是 Observer 观察者角色。出版者-主题,订阅者-观察者。
代码实现
实现一
首先我们定义 Subject 主题角色报社 NewspaperSubject。主要提供 注册观察者、删除观察者、通知所有观察者方法。
定义包报纸对象 Newspaper
public class Newspaper implements Serializable {
private LocalDateTime reportTime;
private String data;
public LocalDateTime getReportTime() {
return reportTime;
}
public void setReportTime(LocalDateTime reportTime) {
this.reportTime = reportTime;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return "Newspaper{" +
"reportTime=" + reportTime +
", data='" + data + '\'' +
'}';
}
}
定义主题对象
public interface NewspaperSubject {
/**
* 注册观察者
* @param observer
*/
void registerObserver(Observer observer);
/**
* 移除观察者
* @param observer
*/
void removeObserver(Observer observer);
/**
* 通知所有观察者
* @param data
*/
void notifyObservers(Newspaper data);
}
同时实现该主题,代码如下:
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ChinaNewspaperSubject implements NewspaperSubject {
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private List<Observer> observers;
public ChinaNewspaperSubject() {
this.observers = new ArrayList<>();
}
public void setChange() {
Newspaper newspaper = new Newspaper();
newspaper.setReportTime(LocalDateTime.now());
newspaper.setData("发布新闻");
notifyObservers(newspaper);
}
@Override
public void registerObserver(Observer observer) {
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
try {
writeLock.lock();
observers.add(observer);
} finally {
writeLock.unlock();
}
}
@Override
public void removeObserver(Observer observer) {
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
try {
writeLock.lock();
observers.remove(observer);
} finally {
writeLock.unlock();
}
}
@Override
public void notifyObservers(Newspaper data) {
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
try {
readLock.lock();
observers.forEach(item -> item.notice(data));
} finally {
readLock.unlock();
}
}
}
然后定义观察者(Observer)角色 也就是报纸订阅者
public interface Observer {
/**
* 接收主题发布的更新通知
*/
void notice(Newspaper data);
}
定义观察者具体实现类:一个香港用户订阅报纸
public class HonKongObserver implements Observer {
@Override
public void notice(Newspaper data) {
System.out.println("我收到报社的报纸了:" + "内容是" + data.toString());
}
}
最后测试
public class Test {
public static void main(String[] args) {
//创建报社
ChinaNewspaperSubject newspaperSubject = new ChinaNewspaperSubject();
//创建订阅者
HonKongObserver honKongObserver = new HonKongObserver();
//订阅者关注该报社
newspaperSubject.registerObserver(honKongObserver);
//报社发布新报纸。所有逇订阅者收到报纸
newspaperSubject.setChange();
}
}
方式二:通过JDK内置的实现
我们的JDK内部与为我们实现了观察者模式。只不过我们的主题需要继承 jdk 中的主题,观察者实现对应的Observer 接口。之前我们说过要多用组合与委托。面向接口编程而不是实现。内置的主题我们必须继承,若想更灵活其实我们自己定义主题接口会更好,并且也并不难。
首先我们的主题要先继承 Observerble ,这是jdk内置的。
public class NumsObservable extends Observable {
public final static Integer ODD = 1;
public final static Integer EVEN = 2;
private int data = 0;
/**
* 获取对象数据
*
* @return
*/
public int getData() {
return data;
}
/**
* 设置数据变化
* 根据数据的变化设置相应的标志变量,通知给订阅者
*
* @param data
*/
public void setData(int data) {
this.data = data;
Integer flag = EVEN;
if ((this.data & 0x0001) == 1) {
flag = ODD;
}
setChanged();
// 将变化的变化的标识变量通知给订阅者
notifyObservers(flag);
}
}
接着定义我们的观察者:分别是偶数与奇数订阅者。
/**
* 奇数内容订阅类
* Created by jianqing.li on 2017/6/8.
*/
public class OddObserver implements Observer {
/**
* 继承自Observer接口类,update的方法的实现
*
* @param o 主题对象
* @param arg notifyObservers(flag);传来的参数,即是标识变量
*/
@Override
public void update(Observable o, Object arg) {
if (arg == NumsObservable.ODD) {
NumsObservable numsObservable = (NumsObservable) o;
System.out.println("Data has changed to ODD number " + numsObservable.getData());
}
}
}
/**
* 偶数内容订阅类:订阅主题的内容的偶数变化
* Created by jianqing.li on 2017/6/8.
*/
public class EvenObserver implements Observer {
/**
* 继承自Observer接口类,update的方法的实现
*
* @param o 主题对象
* @param arg notifyObservers(flag);传来的参数,即是标识变量
*/
@Override
public void update(Observable o, Object arg) {
if (arg == NumsObservable.EVEN) {
NumsObservable numsObservable = (NumsObservable) o;
System.out.println("Data has changed to EVEN number " + numsObservable.getData());
}
}
}
编写测试
public class ObserverTest {
public static void main(String[] args) {
// 创建主题
NumsObservable numsObservable = new NumsObservable();
//创建订阅者
OddObserver oddObserver = new OddObserver();
EvenObserver evenObserver = new EvenObserver();
numsObservable.addObserver(oddObserver);
numsObservable.addObserver(evenObserver);
//修改主题内容,触发notifyObservers
numsObservable.setData(11);
numsObservable.setData(12);
numsObservable.setData(13);
}
}
总结
- java.util.Observable 的阴暗面:它是一个类,我们的主题必须继承它,我们若是想继承其他类就无能为力了。毕竟 Java 不能多重继承。
- 在哪里有观察者模式的运用?嘿嘿谷歌的 Guava 类库中的 EventBus 事件总线使用的就是观察者模式。
- Spring 中的 事件传播 也是如此。
- 主要作用就是为交互对象之间的松耦合。当一个对象改变,依赖它的对象都会收到通知。主题并不知道观察者的细节,只知道观察者实现了 Observer 接口。
客官觉得不过可以订阅 公众号 JavaStorm 与点赞,你的订阅就是最好的观察者应用。
加我微信进去专属技术群,欢迎读者提出意见,喜欢请关注博客和个人公众号:码哥字节