1 引言
观察者模式,又名发布订阅模式,是一个一对多的关系,当被观察者发生某种变化,对应其观察者做出相应的改变。比如说,某学校研究生实验室有2个学生,2个学生某个上午在实验室,A在玩游戏,B在看电影,但是害怕老板进屋把他们逮个正着,怎么办?这2个学生和楼下安保处的门卫关系都特别好,他们就和门卫说了,如果看到他们老板进楼,就赶紧通知他们一声,他们好转换进入学习模式。在这个例子中,门卫就是被观察者(可不是老板啊),也就是事件的发布者,2个学生就是观察者,只要在门卫那里登了记,门卫一群发消息,就能收到通知,做出改变。
2 观察者模式的优点
解耦。观察者模式所做的工作就是解除耦合,让观察者与被观察者都依赖于各自上层的抽象,而不是依赖于具体实现,从而使得观察者与被观察者的具体实现代码变化不会互相影响。
3 适用场景
当一个对象的改变需要引起其他对象的改变时,这里是指状态的变化,而不是指代码的变化。尤其是一个对象变化但却不知道会引起多少其他对象的变化。
4 观察者代码示例
4.1 抽象的被观察者
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 10:47
* @description: 通知者的接口
**/
public interface Subject {
/**
* 添加观察者
*
* @param observer 观察者
*/
void addObserver(Observer observer);
/**
* 删除观察者
*
* @param observer 观察者
*/
void deleteObserver(Observer observer);
/**
* 通知观察者
*
* @param msg 通知信息
*/
void notifyAll(String msg);
}
4.2 具体的观察者
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 11:02
**/
public class ConcreteSubject implements Subject{
//确保观察者不会重复存在
Set<Observer> observers = new HashSet<>();
@Override
public void addObserver(Observer observer) {
if (observer == null){
throw new NullPointerException("观察者不允许为空");
}
observers.add(observer);
}
@Override
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyAll(String msg) {
for (Observer o: observers) {
o.update(this, msg);
}
}
}
4.3 抽象的观察者
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 10:47
* @description: 观察者的接口
**/
public interface Observer {
/**
* 接收到通知,更新自我的状态
*
* @param subject 通知者
* @param msg 通知信息
*/
void update(Subject subject, String msg);
}
4.4 具体观察者1-玩游戏
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 11:09
**/
public class GameObserver implements Observer{
@Override
public void update(Subject subject, String msg) {
System.out.println("这里是" + subject.getClass() + "," + msg + "请停止打游戏");
}
}
4.5 具体观察者2-看电影
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 11:09
**/
public class MovieObserver implements Observer{
@Override
public void update(Subject subject, String msg) {
System.out.println("这里是" + subject.getClass() + "," + msg + "请停止看电影");
}
}
4.6 主程序
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 11:18
**/
public class MainClass {
public static void main(String[] args) throws Exception {
ConcreteSubject concreteSubject = new ConcreteSubject();
GameObserver gameObserver = new GameObserver();
MovieObserver movieObserver = new MovieObserver();
concreteSubject.addObserver(gameObserver);
concreteSubject.addObserver(movieObserver);
concreteSubject.notifyAll("有内鬼");
}
}
4.7 执行结果
5 观察者模式的不足
1.尽管观察者与被观察者都做了抽象,但是接口之间彼此还是有耦合;例如,我们在定义Subject的接口是,添加观察者与删除观察者还是耦合了观察者Observer接口。
2.被观察者发布的方法是固定不变的,玩游戏的同学收到通知想要做的是关闭游戏,然后在老师来之前开溜,看电影的同学想要在老师来之前关闭电影,打开idea刷个好印象,现在是不能实现的,被观察者统一调用update方法。
6 委托事件模型(DEM)
个人理解,委托,就是把以前自己需要做的事情,转给别人做。主题(Subject)角色负责发布事件,而观察者向特定的主题订阅它所感兴趣的事件,当一个具体主题产生一个事件时,他就会通知所有感兴趣的订阅者。而且能够动态的订阅和取消。DEM中发布者叫做事件源(event source),而订阅者叫做事件监听器(event listener)。
具体如何实现了?我们首先定义一个事件Event,Event的任务就是执行传入进来的对象的方法,这样,对象不同,方法可以不同,这就解决了缺点的第二个问题-观察者固定不变的方法。再定义一个EventHandler类,负责添加事件与发布事件,而Subject只需要传入EventHandler类,添加订阅的事件与发布消息,这样就解决了第一个关于耦合的问题。
7 委托事件代码实现
7.1 Event类
**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 14:14
**/
@Data
@Setter
@Getter
public class Event {
/**
* 委托事件者
*/
private Object object;
/**
* 委托执行的方法
*/
private String methodName;
/**
* 委托执行的方法参数
*/
private Object[] params;
/**
* 执行方法的参数类型
*/
private Class[] paramTypes;
public Event(){
//无参构造
}
public Event(Object object, String methodName, Object...params){
this.object = object;
this.methodName = methodName;
this.params = params;
contractParamTypes(this.params);
}
/**
*构造参数类型数组
*
*/
private void contractParamTypes(Object[] params){
this.paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
this.paramTypes[i] = params[i].getClass();
}
}
/**
*执行委托的方法
*
*/
public void invoke() throws Exception {
Method method = object.getClass().getMethod(this.methodName, this.paramTypes);
method.invoke(this.object, this.params);
}
}
7.2 EventHandler类
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 14:37
**/
@Data
public class EventHandler {
private Set<Event> events;
public EventHandler(){
this.events = new HashSet<>();
}
//相当于添加一个事件,关联一个观察者
public void addEvent(Object object, String methodName, java.lang.Object[] args){
events.add(new Event(object, methodName, args));
}
//发布事件
public void subscribeNotice() throws Exception {
for (Event e: events){
e.invoke();
}
}
}
7.3 Source被观察者抽象接口
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 14:56
**/
public interface Source {
/**
* 添加观察者
*
* @param object 观察者实例
* @param methodName 执行方法名称
* @param args 方法的参数
*/
void addListener(Object object, String methodName, Object...args);
/**
* 通知
* @throws Exception
*/
void subscribeNotice() throws Exception;
}
7.4 Source具体实现类ConcreteSource
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 14:59
**/
public class ConcreteSource implements Source{
private final EventHandler eventHandler = new EventHandler();
@Override
public void addListener(Object object, String methodName, Object...args) {
eventHandler.addEvent(object, methodName, args);
}
@Override
public void subscribeNotice() throws Exception {
eventHandler.subscribeNotice();
}
}
7.5 观察者1-看电视选手TvObserver
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 15:18
**/
public class TvObserver {
public void stopWatchTv(String reason){
System.out.println("赶快别看电视了, " + reason);
}
}
7.6 观察者2-摸鱼选手AttachFishObserver
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 15:18
**/
public class AttachFishObserver {
public void stopAttachFish(Date date) {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(f.format(date));
System.out.println("赶快别看摸鱼了, 看看时间啊,我的哥");
}
}
7.7 主函数
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-08-31 11:18
**/
public class MainClass {
public static void main(String[] args) throws Exception {
ConcreteSubject concreteSubject = new ConcreteSubject();
GameObserver gameObserver = new GameObserver();
MovieObserver movieObserver = new MovieObserver();
concreteSubject.addObserver(gameObserver);
concreteSubject.addObserver(movieObserver);
concreteSubject.notifyAll("有内鬼");
System.out.println("---------------------分界线----------------------");
ConcreteSource concreteSource = new ConcreteSource();
TvObserver tvObserver = new TvObserver();
AttachFishObserver attachFishObserver = new AttachFishObserver();
concreteSource.addListener(tvObserver, "stopWatchTv", "你老妈回来了");
concreteSource.addListener(attachFishObserver, "stopAttachFish", new Date());
concreteSource.subscribeNotice();
}
}
7.8 执行结果
8 总结
本文依据自己的理解,介绍了观察者模式,通过观察者模式的不足,引出委托事件,给出代码与执行结果。
9 引用
4.《大话设计模式》