1 Star 0 Fork 1

wow / java-notes

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
观察者模式.md 8.20 KB
一键复制 编辑 原始数据 按行查看 历史
wow 提交于 2020-09-01 19:25 . 设计模式

概念

观察者模式(Observer Pattern),定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

该模式属于行为模式,观察者模式也叫作发布订阅模式。

使用场景

观察者模式主要用于,在行为之间建立一套触发机制。如微信朋友圈的通知、MQ等

手写观察者模式

我们模拟一下鼠标点击触发事件。

我们首先需要一个事件模型,来表示事件源

@Data
@Accessors(chain = true)
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 class EventLisenter {

    //保存事件的容器
    protected Map<String, Event> events = new HashMap<String, Event>();

    //将事件名称和回调对象添加到事件容器中
    //事件名称和一个目标对象来触发事件
    public void addLisenter(String eventType, Object target) {
        try {
            this.addLisenter(
                    eventType,
                    target,
                    target.getClass()
                        .getMethod("on" + toUpperFirstCase(eventType), Event.class));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void addLisenter(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);
    }
}

接下来我们定义鼠标的行为以及回调定义

public class Mouse extends EventLisenter {

    public void click() {
        System.out.println("调用单击方法");
        this.trigger(MouseEventType.ON_CLICK);
    }

    public void doubleClick() {
        System.out.println("调用双击方法");
        this.trigger(MouseEventType.ON_DOUBLE_CLICK);
    }

    public void up() {
        System.out.println("调用弹起方法");
        this.trigger(MouseEventType.ON_UP);
    }

    public void down() {
        System.out.println("调用按下方法");
        this.trigger(MouseEventType.ON_DOWN);
    }

    public void move() {
        System.out.println("调用移动方法");
        this.trigger(MouseEventType.ON_MOVE);
    }

    public void wheel() {
        System.out.println("调用滚动方法");
        this.trigger(MouseEventType.ON_WHEEL);
    }

    public void over() {
        System.out.println("调用悬停方法");
        this.trigger(MouseEventType.ON_OVER);
    }

    public void blur() {
        System.out.println("调用获焦方法");
        this.trigger(MouseEventType.ON_BLUR);
    }

    public void focus() {
        System.out.println("调用失焦方法");
        this.trigger(MouseEventType.ON_FOCUS);
    }
}

回调定义

public interface MouseEventType {
    //单击
    String ON_CLICK = "click";
    //双击
    String ON_DOUBLE_CLICK = "doubleClick";
    //弹起
    String ON_UP = "up";
    //按下
    String ON_DOWN = "down";
    //移动
    String ON_MOVE = "move";
    //滚动
    String ON_WHEEL = "wheel";
    //悬停
    String ON_OVER = "over";
    //失焦
    String ON_BLUR = "blur";
    //获焦
    String ON_FOCUS = "focus";
}

具体回调

public class MouseEventCallback {

    public void onClick(Event e) {
        System.out.println("===========触发鼠标单击事件==========" + "\n" + e);
    }

    public void onDoubleClick(Event e) {
        System.out.println("===========触发鼠标双击事件==========" + "\n" + e);
    }

    public void onUp(Event e) {
        System.out.println("===========触发鼠标弹起事件==========" + "\n" + e);
    }

    public void onDown(Event e) {
        System.out.println("===========触发鼠标按下事件==========" + "\n" + e);
    }

    public void onMove(Event e) {
        System.out.println("===========触发鼠标移动事件==========" + "\n" + e);
    }

    public void onWheel(Event e) {
        System.out.println("===========触发鼠标滚动事件==========" + "\n" + e);
    }

    public void onOver(Event e) {
        System.out.println("===========触发鼠标悬停事件==========" + "\n" + e);
    }

    public void onBlur(Event e) {
        System.out.println("===========触发鼠标失焦事件==========" + "\n" + e);
    }

    public void onFocus(Event e) {
        System.out.println("===========触发鼠标获焦事件==========" + "\n" + e);
    }
}

测试执行

public class MouseEventTest {
    public static void main(String[] args) {
        try {

            //定义事件
            MouseEventCallback callback = new MouseEventCallback();

            //注册事件
            Mouse mouse = new Mouse();
            mouse.addLisenter(MouseEventType.ON_CLICK, callback);
            mouse.addLisenter(MouseEventType.ON_MOVE, callback);
            mouse.addLisenter(MouseEventType.ON_WHEEL, callback);
            mouse.addLisenter(MouseEventType.ON_OVER, callback);

            //调用点击方法,触发事件
            mouse.click();

            //失焦事件,触发事件
            mouse.blur();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出

调用单击方法
===========触发鼠标单击事件==========Event(xxxxxxxxxxxxxx)
调用获焦方法

我们可以看到 click 方法的时间被触发,而 blur 的时间未被触发,这是因为我们没有在监听器中注册 blur 对应的事件。

基于Guava实现观察者模式

首先引入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("执行 String 方法,传入的参数是:" + str);
    }

    @Subscribe//订阅注解
    public void subscribe(Integer a) {
        //业务逻辑
        System.out.println("执行 Integer 方法,传入的参数是:" + a);
    }
}

测试

public static void main(String[] args) {
    //事件总线
    EventBus eventbus = new EventBus();
    //事件定义与注册
    GuavaEvent guavaEvent = new GuavaEvent();
    eventbus.register(guavaEvent);
    //发布事件
    eventbus.post(1);

    eventbus.post("tom");
}

输出

执行 Integer 方法,传入的参数是:1
执行 String 方法,传入的参数是:tom

我们可以看到 EventBus 可以自动匹配参数类型。

还有一点需要说明,被 @Subscribe 修饰的方法有且只能有一个参数

其他
1
https://gitee.com/superwow/java-notes.git
git@gitee.com:superwow/java-notes.git
superwow
java-notes
java-notes
master

搜索帮助