观察者模式的应用场景
观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新,属于行为型模式。观察者模式有时也叫做发布订阅模式。观察者模式主要用于在关联行为之间建立一套触发机制的场景。观察者模式在现实生活应用也非常广泛,比如:微信朋友圈动态通知、GPser 生态圈消息通知、邮件通知、广播通知、桌面程序的事件响应等(如下图)。
MQ,异步队列等都是观察者模式的一种实现,JDK 本身就提供这样的API
这里我们以学生在论坛问题为例,学生在网站提问后,老师可以收到学生的提示 用jdkAPI实现:
学生类 继承Observable:
public class Student extends Observable {
private String name ;
public Student(String name) {
this.name = name;
}
public void publishQue(Question ques){
//发布完问题就去通知所有监听者
System.out.println("发布问题:"+ques.getContent());
setChanged();
notifyObservers(ques);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
问题类:
public class Question {
private String userName;
private String content;
public Question(String userName, String content) {
this.userName = userName;
this.content = content;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
老师类 实现 Observer :
public class Teacher implements Observer {
private String name ;
public Teacher(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
Student stu=(Student) o;
Question que= (Question) arg;
System.out.println("老师 "+this.name+" 收到了 "+((Student) o).getName()+" 的问题:"+((Question) arg).getContent());
}
}
测试类
public class ObserverTest {
public static void main(String[] args) {
Student stu= new Student("小张");
Question que= new Question(stu.getName(),"地球为什么是圆的?");
Teacher zhang = new Teacher("zhang");
Teacher wang = new Teacher("wang");
stu.addObserver(zhang);
stu.addObserver(wang);
stu.publishQue(que);
}
}
运行结果,当发布问题后,老师都收到了通知:
JDK 源码中,观察者模式也应用非常多。例如java.awt.Event 就是观察者模式的一种,只不过Java 很
少被用来写桌面程序。我们自己用代码来实现一下,感受一下原理
Event类:
//定义谁在什么时候触发什么事件 回调需要调用的target的哪个目标方法
public class Event {
private Object source;
private Object target;
private Date time;
private String trigger ;
private Method callback;
public Event(Object target, Method callback) {
this.target = target;
this.callback = callback;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getTrigger() {
return trigger;
}
public void setTrigger(String trigger) {
this.trigger = trigger;
}
public Method getCallback() {
return callback;
}
public void setCallback(Method callback) {
this.callback = callback;
}
}
ButtonEventType 定义事件类型
//定义事件类型
public interface ButtonEventType {
//单击
String ON_CLICK = "click";
//双击
String ON_DOUBLE_CLICK = "doubleClick";
//弹起
String ON_UP = "up";
}
定义按钮事件回调接口ButtonEventCallback
public interface ButtonEventCallback {
public void onClick(Event e);
public void onDoubleClick(Event e);
public void onUp(Event e);
}
监听实现的关键 EventLisenter ,实现了添加监听的操作,只要组件继承了这个类 就可以进行监听操作了。
//监听器 观察者和被观察者的桥梁
public class EventLisenter {
protected Map<String,Event> events = new HashMap<String,Event>();
public void addListener(String eventType,Object target){
try{
this.addListener(eventType,target,target.getClass().getMethod("on"+toUpperFirstCase(eventType),Event.class));
}catch (Exception e){
e.printStackTrace();
}
}
private void addListener(String eventType, Object target, Method method) {
events.put(eventType,new Event(target,method));
}
private void trigger(Event event){
event.setSource(this);
event.setTime(new Date());
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)){
trigger(this.events.get(trigger));
}
}
private String toUpperFirstCase(String str){
char[] chars = str.toCharArray();
chars[0] -= 32;
return String.valueOf(chars);
}
}
定义 按钮类,
public class Button extends EventLisenter {
public void click(){
System.out.println("调用单击方法");
this.trigger(ButtonEventType.ON_CLICK);
}
public void doubleClick(){
System.out.println("调用双击方法");
this.trigger(ButtonEventType.ON_DOUBLE_CLICK);
}
public void up(){
System.out.println("调用弹起方法");
this.trigger(ButtonEventType.ON_UP);
}
public static void main(String[] args) {
Button m= new Button();
m.addListener(ButtonEventType.ON_CLICK, new ButtonEventCallback() {
@Override
public void onClick(Event e) {//onClick
System.out.println("按钮被点击");
}
@Override
public void onDoubleClick(Event e) {
}
@Override
public void onUp(Event e) {
}
});
m.click();
}
执行测试代码,按钮click 被调用后 我们的回调代码也执行了。结果如下:
实现原理分析:定义好事件类型、回调接口。然后需要被监听的组件中增加监听支持逻辑,用一个集合保存监听器,当组件的方法被执行以后 到集合中查看是否有该方法的监听器,如果有则调用监听器的回调方法。
观察者模式在源码中的应用
Spring 中的ContextLoaderListener 实现了ServletContextListener 接口,ServletContextListener 接口又继承了EventListener,在JDK 中EventListener 有非常广泛的应用。我们可以看一下源代码,ContextLoaderListener:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
ServletContextListener
public interface ServletContextListener extends EventListener {
public void contextInitialized(ServletContextEvent sce);
public void contextDestroyed(ServletContextEvent sce);
}
EventListener
public interface EventListener {
}
基于Guava API 轻松落地观察者模式
在这里,推荐给大家一个实现观察者模式非常好用的框架。API 使用也非常简单,举个例子,先引入maven 依赖包:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
测试类代码:
public class GuavaEvent {
@Subscribe
public void subscribe(String str){
//业务逻辑
System.out.println("执行subscribe 方法,传入的参数是:" + str);
}
public static void main(String[] args) {
EventBus eb= new EventBus( );
GuavaEvent ge= new GuavaEvent();
eb.register(ge);
eb.post("test");
}
}
观察者模式的优缺点
优点:
1、观察者和被观察者之间建立了一个抽象的耦合。
2、观察者模式支持广播通信。
缺点:
1、观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度。
2、使用要得当,要避免循环调用。