1、博客内容均出自于咕泡学院架构师第三期
2、架构师系列内容:架构师学习笔记(持续更新)
0、观察者模式(Observer Pattern)
观察者模式也叫发布订阅模式。定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新。属于行为型模式。
优缺点:
优点:
1、观察者和被观察者之间建立了一个抽象的耦合。
2、观察者模式支持广播通信。
缺点:
1、观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度。
2、使用要得当,要避免循环调用。
生活应用场景:
微信朋友圈动态通知,知乎邀请答主回答问题,邮件通知,广播通知,桌面程序的事件等。
1、简单案例
知乎社区邀请答主答题,我们通过jdk提供的API来实现。
首先创建Zhihu类:
package com.jarvisy.demo.pattern.observer.advice;
import lombok.Data;
import java.util.Observable;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 21:45
* @description :jdk 提供的一种观察者的实现方式 这是被观察者
*/
@Data
public class Zhihu extends Observable {
private String name = "知乎社区";
private Zhihu() {
}
private static Zhihu zhihu = null;
public static Zhihu getInstance() {
//这里不考虑多线程的问题
if (null == zhihu) {
zhihu = new Zhihu();
}
return zhihu;
}
public void publishQuestion(Question question) {
System.out.println(question.getUserName() + "在" + this.name + "上提交了一个问题");
setChanged(); //设置一个内部标志位注明数据发生变化
notifyObservers(question);//会调用一个列表中的所有Observe的update方法,默认无参,会自动添加一个null,如果update方法实现中有调用arg,则在这里需要设置对象 ,否则会导致update调用的时候出现空指针异常
}
}
创建Answerer:
package com.jarvisy.demo.pattern.observer.advice;
import java.util.Observable;
import java.util.Observer;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 22:12
* @description :这是观察者,被观察者在标志位被修改后就会通知到观察者,调用观察者的update方法
*/
public class Answerer implements Observer {
private String name;
public Answerer(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
Zhihu zhihu = (Zhihu) o;
Question question = (Question) arg;
System.out.println("================================================");
System.out.println(name + "答主,你好!\n" +
"您收到了一个来自“" + zhihu.getName() + "”的提问,希望您解答,问题内容如下:\n" +
question.getContent() + "\n" +
"提问者:" + question.getUserName());
}
}
创建测试代码:
package com.jarvisy.demo.pattern.observer.advice;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 22:34
* @description :
*/
public class ObserverTest {
public static void main(String[] args) {
Zhihu zhihu = Zhihu.getInstance();
Answerer use1 = new Answerer("User1");
Answerer use2 = new Answerer("User2");
Question question = new Question();
question.setUserName("小明");
question.setContent("观察者设计模式适用于哪些场景");
zhihu.addObserver(use1);//添加一个观察者 相当于@一个人
zhihu.addObserver(use2);//添加一个观察者 相当于@一个人
//这里需要先添加观察者,才能变更数据修改标识位,否则无法通知到观察者
zhihu.publishQuestion(question);
}
}
原理解读:
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public void notifyObservers() {
notifyObservers(null);
}
//核心方法就在这里
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed) //这里就是setChanged方法设置数据变更标识位,默认为false,如果不调用setChanged 则不会发送通知
return;
arrLocal = obs.toArray();
clearChanged();//通知后又把标志位还原回来
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg); //这里就是调用通知,如果在标识位变更前不进行添加通知者,这里进不会进行for循环。 arg如果有调用的话,在notifyObservers方法就需要设置对象,否则会失败
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void clearChanged() {
changed = false;
}
protected synchronized void setChanged() {
changed = true;
}
}
2、模拟java.awt.Event观察者模式实现
java.awt.Event 就是观察者模式的一种,只不过 Java 很少被用来写桌面程序。我们自己用代码来实现一下。
首先创建Event类:
package com.jarvisy.demo.pattern.observer.events.core;
import java.lang.reflect.Method;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:15
* @description :监听器的一种包装,标准事件源格式的定义
*/
public class Event {
//事件源,事件是由谁发起的保存起来
private Object source;
//事件触发,要通知谁
private Object target;
//事件触发,要做什么动作,回调
private Method callback;
//事件的名称,触发的是什么事件
private String trigger;
//事件触发的时间
private long time;
public Event(Object target, Method callback) {
this.target = target;
this.callback = callback;
}
public Event setSource(Object source) {
this.source = source;
return this;
}
public Event setTime(long time) {
this.time = time;
return this;
}
public Object getSource() {
return source;
}
public Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
}
public long getTime() {
return time;
}
public Object getTarget() {
return target;
}
public Method getCallback() {
return callback;
}
@Override
public String toString() {
return "Event{" + "\n" +
"\tsource=" + source.getClass() + ",\n" +
"\ttarget=" + target.getClass() + ",\n" +
"\tcallback=" + callback + ",\n" +
"\ttrigger='" + trigger + "',\n" +
"\ttime=" + time + "'\n" +
'}';
}
}
创建 EventLisenter (相当于Observable类)类:
package com.jarvisy.demo.pattern.observer.events.core;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:13
* @description :监听器,它就是观察者
*/
public class EventListener {
//JDK底层的Listener通常也是这样来设计的
protected Map<String, Event> events = new HashMap<String, Event>();
//事件名称和一个目标对象来触发事件
public void addListener(String eventType, Object target) {
try {
if (null == target) return;//如果回调事件为null ,则不添加
this.addListener(eventType, target, target.getClass().getMethod("on" + toUpperFirstCase(eventType), Event.class));
} catch (Exception e) {
e.printStackTrace();
}
}
public void addListener(String eventType, Object target, Method callback) {
//注册事件
events.put(eventType, new Event(target, callback));
}
//触发,只要有动作就触发
private void trigger(Event event) {
event.setSource(this);
event.setTime(System.currentTimeMillis());
try {
//发起回调
if (event.getCallback() != null) {
//用反射调用它的回调函数
event.getCallback().invoke(event.getTarget(), event);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//事件名称触发
protected void trigger(String trigger) {
if (!this.events.containsKey(trigger)) {
return;
}
trigger(this.events.get(trigger).setTrigger(trigger));
}
//逻辑处理的私有方法,首字母大写
private String toUpperFirstCase(String str) {
char[] chars = str.toCharArray();
chars[0] -= 32;
return String.valueOf(chars);
}
}
创建MouseEventType:
package com.jarvisy.demo.pattern.observer.events.mouseevent;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:17
* @description :这里可以换成枚举类,只是单纯定义属性,没有其他意义
*/
public class MouseEventType {
//单击
public static String ON_CLICK = "click";
//获焦
public static String ON_FOCUS = "focus";
}
创建Mouse类:
package com.jarvisy.demo.pattern.observer.events.mouseevent;
import com.jarvisy.demo.pattern.observer.events.core.EventListener;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:16
* @description :
*/
public class Mouse extends EventListener {
public void click() {
System.out.println("调用单击方法");
this.trigger(MouseEventType.ON_CLICK);
}
public void focus() {
System.out.println("调用失焦方法");
this.trigger(MouseEventType.ON_FOCUS);
}
}
创建回调方法MouseEventCallback类:
package com.jarvisy.demo.pattern.observer.events.mouseevent;
import com.jarvisy.demo.pattern.observer.events.core.Event;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:17
* @description :自己写的逻辑,用于回调
*/
public class MouseEventCallback {
public void onClick(Event e) {
System.out.println("===========触发鼠标单击事件==========" + "\n" + e);
}
public void onFocus(Event e) {
System.out.println("===========触发鼠标获焦事件==========" + "\n" + e);
}
}
测试代码:
package com.jarvisy.demo.pattern.observer.events;
import com.jarvisy.demo.pattern.observer.events.mouseevent.Mouse;
import com.jarvisy.demo.pattern.observer.events.mouseevent.MouseEventCallback;
import com.jarvisy.demo.pattern.observer.events.mouseevent.MouseEventType;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:19
* @description :
*/
public class MouseEventTest {
public static void main(String[] args) {
MouseEventCallback callback = new MouseEventCallback();
Mouse mouse = new Mouse();
mouse.addListener(MouseEventType.ON_CLICK, callback);
mouse.addListener(MouseEventType.ON_FOCUS, callback);
mouse.click();
mouse.focus();
}
}
3、观察者模式在源码中的应用
Spring 中的 ContextLoaderListener 实现了 ServletContextListener 接口,ServletContextListener 接口又继承了 EventListener,在 JDK 中 EventListener 有非常广泛的应用。我们可以看一下源代码,ContextLoaderListener:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
ServletContextListener:
package javax.servlet;
import java.util.EventListener;
public interface ServletContextListener extends EventListener {
public void contextInitialized(ServletContextEvent sce);
public void contextDestroyed(ServletContextEvent sce);
}
EventListener:
package java.util;
public interface EventListener {
}
4、基于GuavaApi轻松实现观察者模式
Guava 是一个实现观察者模式非常好的框架。
maven:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
创建侦听事件:
package com.jarvisy.demo.pattern.observer.guava;
import com.google.common.eventbus.Subscribe;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:49
* @description :
*/
public class GuavaEvent {
@Subscribe
public void subscribe(String str) {
System.out.println("执行subscribe方法,传入的参数是:" + str);
}
}
创建测试类:
package com.jarvisy.demo.pattern.observer.guava;
import com.google.common.eventbus.EventBus;
import org.springframework.web.context.ContextLoaderListener;
/**
* @author :Jarvisy
* @date :Created in 2020/9/22 23:50
* @description :
*/
public class GuavaEventTest {
public static void main(String[] args) {
//消息总线
EventBus eventBus = new EventBus();
GuavaEvent guavaEvent = new GuavaEvent();
eventBus.register(guavaEvent);
eventBus.post("Jarvis");
}
}
Guava 的简单应用,这里不再做更深的研究。以后有时间在看看。