前言

观察者模式有另一个名字,叫发布订阅模式,之前的总结有点过于简单,理解的并不深入,这里重新梳理一个实例,重新进行总结。之前的理解:观察者模式

实例

实际开发工作中,写代码算是核心工作,但是让我们心里最不安的,依旧是半夜的系统报警。前几天思考观察者设计模式,发现其实这个实例貌似就和我们处理半夜的线上系统异常类似,这里就以这个场景来说明一下观察者模式。

半夜修复线上bug的小李,为观察者。线上运行的系统可以比喻成被观察者。先给出这两个实例代码。

1、线上运行的系统(被观察者)

public class ServerOnline {

    public void runNormal(){
        System.out.println("服务器正常运行");
    }

    public void runException(){
        System.out.println("服务器运行出现异常");
    }

}

2、程序员小李(观察者) 

public class CoderLi {

    public void problemMessage(){
        System.out.println("程序出现了线上问题,请及时处理");
    }
}

现在被观察者和观察者都有了 ,那么问题来了。1、出现问题了如何通知小李?2、小李收到的通知需要包含一些具体的内容,但是上面的Message方法中,貌似小李得不到任何信息,自然小李也无法处理问题

针对问题一,无非就是在runException的时候调用CoderLi中的problemMessage方法即可。针对问题二,我们需要封装先关的消息作为参数,传递给problemMessage中,为此我们需要引入一个类——Message

3、Message实体

package com.learn.designModel.ObserverPattern.OnlineProblem;

import java.lang.reflect.Method;

/**
 * autor:liman
 * comment: 被观察者与观察者之间传递的消息实体
 */
public class Message {

    private Object source; //事件源,被观察者
    private Object target; //回调的目标对象,观察者
    private String event; //事件类型
    private Method callback; //回调的处理方法,通常是target对象中的某个方法
    private Long occurTime;

    public Message(Object target, Method callback) {
        this.target = target;
        this.callback = callback;
    }

    //这里省略getter/setter方法

    @Override
    public String toString() {
        return "Message{" +"\n"+
                "source=" + source +"\n"+
                ", target=" + target +"\n"+
                ", event='" + event + '\'' +"\n"+
                ", callback=" + callback +"\n"+
                ", occurTime=" + occurTime +"\n"+
                '}';
    }
}

程序员小李作出如下修改:

public class CoderLi {

    public void problemMessage(Message message){
        System.out.println("程序出现了线上问题,请及时处理");
        System.out.println("异常信息为:"+message);
    }
}

现在问题又来了,线上有问题了,如何自动通知程序员小李呢?这里就引入了MessageListener这个类。

4、MessageListener类

package com.learn.designModel.ObserverPattern.OnlineProblem;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * autor:liman
 * comment:用于关联事件类型与消息体
 */
public class MessageListener {

    private Map<String,Message> eventToMessage = new HashMap<>();

    public void addListener(String messageType,Object target,Method callback){
        eventToMessage.put(messageType,new Message(target,callback));
    }

    /**
     * 内部调用的trigger
     * @param message
     */
    public void trigger(Message message){
        //因为子类会继承这个类,子类就是被观察者,所以这里赋值为this
        message.setSource(this);
        message.setOccurTime(System.currentTimeMillis());
        try {
            message.getCallback().invoke(message.getTarget(),message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @param messageType 消息类型
     */
    public void trigger(String messageType){
        if(!eventToMessage.containsKey(messageType)){
            return;
        }
        Message message = eventToMessage.get(messageType);
        message.setEvent(messageType);
        trigger(message);
    }

}

 那么问题依旧:被观察者如何自动通知观察者呢?也就是程序线上出了问题,如何自动通知程序员小李呢?,为了让被观察者具有这个功能,我们通常需要让观察者继承MessageListener这个类,所以被观察者需要作如下修改:

package com.learn.designModel.ObserverPattern.OnlineProblem;

/**
 * autor:liman
 * comment: 服务器,被观察者
 */
public class ServerOnline extends MessageListener{

    public void runNormal(){
        System.out.println("服务器正常运行");
    }

    public void runException(){
        System.out.println("服务器运行出现异常");
        System.out.println("=================开始进入告警机制================");
        //出了问题,通知观察者(程序员小李)
        this.trigger("ERROR");
    }

}

测试实例:

package com.learn.designModel.ObserverPattern.OnlineProblem;

/**
 * autor:liman
 * comment:
 */
public class OnlineProblemTest {

    public static void main(String[] args) throws NoSuchMethodException {
        CoderLi coderLi = new CoderLi();
        ServerOnline serverOnline = new ServerOnline();
        serverOnline.addListener("ERROR",coderLi,
                coderLi.getClass().getMethod("problemMessage",new Class<?>[]{Message.class}));

        serverOnline.runException();
    }

}

 运行结果:

再谈观察者模式

一些说明:

其实针对上述实例,如果详细类比一下会发现,在之前窗体应用开发中,Message就好比Event实例,ServerOnLine就好比指定的控件,而控件有各种事件,就好比“ERROR” 上述实例中只涉及到了一个事件类型,用固定写成了ERROR,如果稍微复杂一点,可以用枚举。

其实观察者设计模式最大的好处就是解耦,在ServerOnline中,我不需要知道我通知的是谁,只需要触发指定的ERROR事件类型即可。自然就会有程序员小李来处理线上问题,所以这就是解耦的最好体现,在实际应用中,消息队列的解耦思想与这个如出一辙。

JDK的实现

其实观察者模式JDK其实已经为我们提供了实现方式。这里也通过一个简单实例来说明这个问题。就以百度知道上的提问模式来解释JDK的观察者模式

1、提问平台(百度知道)被观察者,需要继承Observable

/**
 * autor:liman
 * comment:
 */
public class Forum extends Observable {

    private String name = "百度知道";

    private static Forum forum = null;

    private Forum(){}

    public static Forum getForum(){
        if(forum == null){
            forum=new Forum();
        }
        return forum;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 发布问题
     */
    public void publicQuestion(Question question){
        System.out.println(question.getUserName()+":发布了一个问题,"+question.getContent()+"!");

        //设置变化
        setChanged();
        notifyObservers(question);
    }
}

2、 解答问题的老师(观察者) 需要实现Observer接口,这个接口中有一个update方法,这个方法类似消息队列使用时候的onMessage方法

/**
 * autor:liman
 * comment:
 */
public class Teacher implements Observer {

    private String name;
    public Teacher(String name){this.name = name;}

    @Override
    public void update(Observable o, Object arg) {
        Forum forum = (Forum)o;
        Question question = (Question)arg;
        System.out.println("================================");
        System.out.println("您收到了一个来自百度知道的消息:"+question.getUserName()+" 的提问:"+question.getContent());
    }
}

3、问题(类似于消息对象) 

/**
 * autor:liman
 * comment:
 */
public class Question {

    private String userName;
    private String 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;
    }
}

测试实例

/**
 * autor:liman
 * comment:
 */
public class JDKObserverTest {

    public static void main(String[] args) {
        Teacher teacher = new Teacher("liman");
        Forum forum = Forum.getForum();

        Question question = new Question();
        question.setContent("你最喜欢的人是谁?");
        question.setUserName("test");

        forum.addObserver(teacher);
        forum.publicQuestion(question);
    }
}

运行结果:

再谈观察者模式

总结

观察者模式同时也是spring中使用的较多的一种设计模式,其优点就在于解耦,之前无法理解监听器与事件源的关系,这篇博客中算是给出了较好的总结。

相关文章: