Java设计模式(三)——观察者模式和监听器

为了实现多个模块之间的联动,最好的方法是使用观察者模式。网上介绍的资料也比较多,今天我就从另一个方面谈谈自己对观察者模式的理解。从JDK提供的支持库里,我们能够找到四个对象:Observable、Observer、EventListener、EventObject。

先模拟一个后台处理过程:

import java.util.Observable;

public class Subject extends Observable {
    private int value;

    public int getValue() {
        return value;
    }

    private void progress() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            value = i;
            setChanged(); // 值发生改变
            notifyObservers(); // 调用所有注册的观察者
        }
    }
    
    public void onStart() {
        progress();
    }

}

稍微对上面的代码做一些解释,顺便介绍一下Observable这个类:

顾名思义“能够被观察的对象”,用户能够继承它并增加自己的定义,它提供了几个方法。

addObserver(Observer o):注册观察者,这个方法的内部其实就是提供了一个队列。将多有观察者储存在队列中,当有事件发生的时候遍历这个队列。

hasChanged()setChanged():当事件发生时,需要调用setChanged()方法,此时hasChanged()返回true否则返回false。

notifyObservers()notifyObservers(Object arg):通知所有观察者可以获取各自需要的变量或只推送某个对象。

下面模拟一个进度条:

public class Progress implements Observer {
    private int value;

    @Override
    public void update(Observable o, Object arg) {
        value = ((Subject)o).getValue();
        System.out.print("#");
    }
    
    public static void main(String[] args) {
        Progress ui = new Progress();
        Subject subject = new Subject();
        subject.addObserver(ui);
        subject.onStart();
    }
}

进度条作为后台程序的观察者需要实现update(Observable o, Object arg)方法。当Subject调用setChanged()后使用notifyObservers()方法会回调update。需要注意,notifyObservers方法隐式调用clearChanged(),因此如果用户试图在update方法中调用o.hasChanged()的时候,只会返回false。

我们再来看一个使用监听器的例子。相对上面的代码来说,实现自己的监听器会稍微复杂一些。但是只要我们先想清楚逻辑就不会有什么难度。

监听器模式将对象分为了三个模块:Source(事件源)、ChangeEvent(事件)、StatusListener(监听器)。事件源可能会触发多个事件,针对不同的事件都可以定义独立的监听器,当事件源触发事件的时候监听器获得通知并实现用户自定义的业务逻辑。相比Observer和Observable的二元实现来说。监听器抽象了事件对象,目的是不同的事件源可能包含相同的触发事件,为了提供更好的内聚处理。监听器的处理逻辑是针对事件本身而言的。

那么也许你会好奇,如果监听的对象是事件本身如何根据不同的事件源提供不同的逻辑呢?秘诀在于事件本身会提供一个事件源对象供监听器判断。

或许现在你反而会更加困惑。不要紧其实当我第一次自己去实现监听器的时候面对这三个模块的逻辑也相当混乱,等你看完代码以后再回来仔细推敲上面的文字会豁然开朗。

先看一下事件源:

public class Source {
    private List<StatusListener> statusListeners = new ArrayList<>();
    
    public void addStatusListener(StatusListener listener) {
        statusListeners.add(listener);
    }
    
    public void onClick() {
        ChangeEvent event = new ChangeEvent(this);
        event.setStatus("click");
        notifyListener(event);
    }
    
    public void onDoubleClick() {
        ChangeEvent event = new ChangeEvent(this);
        event.setStatus("doubleClick");
        notifyListener(event);
    }
    
    public void onMove() {
        ChangeEvent event = new ChangeEvent(this);
        event.setStatus("move");
        notifyListener(event);
    }
    
    private void notifyListener(ChangeEvent event) {
        Iterator<StatusListener> it = statusListeners.iterator();
        while(it.hasNext()) {
            StatusListener listener = it.next();
            listener.changeStatus(event);
        }
    }
}

事件源提供了三个触发事件,分别是点击(click)、双击(doubleclick)、拖拽(move)。这是设计UI的时候经常遇到的三个事件,相信大家不应该陌生。Source没有继承任何接口或父类,完全是一个自定义的类。那么针对三种触发类型,我们接下来需要提供事件。

public class ChangeEvent extends EventObject {
    private String status;
    
    public ChangeEvent(Object source) {
        super(source);
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
    
}

ChangeEvent对象继承EventObject,必须提供一个注册事件源的构造方法。然后在事件中,我们定义了一个字符串变量作为保存状态的接口,更加复杂的逻辑原理也是一样的。

定义好事件以后,我们再定义一个监听器接口。

public interface StatusListener extends EventListener {
    void changeStatus(ChangeEvent event);
}

EventListener接口仅仅是一个标志性接口,内部没有做任何处理。所有逻辑都由使用者自己实现。我们就定义一个changeStatus方法,要求提供ChangeEvent作为参数。

最后返回Source类,添加一个测试用的main方法

public static void main(String[] args) {
        Source source = new Source();
        source.addStatusListener(new StatusListener(){

            @Override
            public void changeStatus(ChangeEvent event) {
                System.out.println(event.getStatus());
            }
        });
        source.onClick();
        source.onDoubleClick();
        source.onMove();
    }

在main方法中我们能够观察到一些区别,首先,监听器并没有提供默认的addListener方法。我们需要自己创建一个保存所有监听对象的队列。

其次,也没有提供notifyListener方法,为了触发监听器我们也需要自己实现遍历队列的逻辑。

最后说一下我对以上两种模式区别的理解。

Observable和Observer属于对象驱动或值驱动。例如进度条的例子,UI界面需要时刻观察后台进度的变化从而动态更新自己。这里的关键词是动态更新。

EventListener和EventObject属于事件驱动或方法驱动。例如按钮的例子,用户造成了某个事件,立刻触发后台程序的响应。这里的关键词是响应。