设计模式解析-1:观察者模式

2,591 阅读4分钟

1、接口回调

在学习观察者模式之前,我们先了解一下接口回调的概念。两者的原理有些类似,理解了接口回调就非常容易理解观察者模式。

所谓接口回调一般应用的场合是:你不知道这个方法什么时候回返回,但是你希望在该方法结束的时候拿到方法执行的结果。常见的,比如一个方法内部开启了线程,而我们希望在线程执行结束的时候拿到线程的执行结果。

在下面的例子中,方法 cal() 定义了一个局部变量 i,随后开启了一个线程,并在线程执行的时候修改了 i 的值。我们希望在线程执行完毕的时候达到 i 的执行结果。

    public int cal() {
        int i;
        new Thread(new Runnable() { 
            Thread.sleep(3000); // throw ... ignore it
            i++; 
        }).start();
        return i;
    }

使用 return 肯定不行,因为方法结束时,线程可能还没有结束,那么 return 返回的结果是无法被预料到的,可能是线程执行完毕之后的,也可能是没有被线程修改就返回了的。

我们可以使用接口回调解决这个问题。我们可以在调用该方法的时候传入一个接口 Callback 的实例。在线程中执行完所有的逻辑之后,我们使用该接口的 call() 方法将 i 回调出来:

    public void cal(Callback callback) {
        int i;
        new Thread(new Runnable() { 
            Thread.sleep(3000); // throw ... ignore it
            i++; 
            if (callback != null) {
                callback.call(i); // 1
            }
        }).start();
    }

当然,接口回调更像是将我的 call() 方法注入到了上述代码中的 1 处。这近似于所谓的函数编程的概念,就是将接口作为一个函数注入到了方法中。

接口回调有非常丰富的应用场景,典型的是一些异步的场景,比如 “监听” 一个按钮的执行结果等等。

2、观察者模式

好了,了解了上面的接口回调之后,我们来看下观察者模式。首先,我们来了解一下观察者设计模式中的一些概念。

如果使用 1 中的的例子来做类比的话,那么 i 就是我们的主题(Subject),我们进行接口回调的类(将实现的 Callback 传入到 cal 方法时所处的类)叫做观察者(Subscriber)

观察者模式UML

上面的是观察者模式的UML模型。这里的 Subject 接口就是主题的接口,而 ConcreteSubkect 是它的具体实现类,即具体的 主题。Observer 接口是观察者的接口,ConcreteObserver 是具体的观察者。一个主题往往会通过列表来维护一系列的观察者,然后当主题发生变化的时候会便利这个列表来通知所有的观察者。所以,这里的 Subject 接口定义了三个方法,从上到下依次用于向主题中添加观察者,从主题中移除观察者以及通知所有的观察者主题的更新。

下面我们给出一份最简单的观察者设计模式的代码:

1.首先是主题的定义类:

    public class ConcreteSubject implements Subject {

        // 通过队列维护观察者列表
        private List<Observer> observers = new LinkedList<>();

        // 注册一个观察者
        @Override
        public void registerObserver(Observer o) {
            observers.add(o);
        }

        // 移除一个观察者
        @Override
        public void removeObserver(Observer o) {
            int i = observers.indexOf(o);
            if (i >= 0) {
                observers.remove(o);
            } 
        }

        // 通知所有观察者主题的更新
        @Override
        public void notifyObservers() {
            for (Observer o : observers) {
                o.method();
            }
        }
    }

2.接下来是观察者的定义类:

    public class ConcreteObserver implements Observer {
        
        // 该观察者订阅的主题
        private Subject subject;

        public ConcreteObserver(Subject subject) {
            this.subject = subject;
            // 将当前观察者添加到主题订阅列表中
            subject.registerObserver(this);
        }
        
        // 当主题发生变化的时候,主题会遍历观察者列表并通过调用该方法来通知观察者
        @Override
        public void method() {
            // ...  
        }
    }

上面就是观察者的基本的实现方式,这里的实现逻辑比较简单,但当你学习更加复杂的观察者设计的之前理解它是很有必要的。

3、总结

观察者与被观察者之间是属于轻度的关联关系,并且是抽象耦合的,这样,对于两者来说都比较容易进行扩展。

观察者模式是一种常用的触发机制,它形成一条触发链,依次对各个观察者的方法进行处理。但同时,这也算是观察者模式一个缺点,由于是链式触发,当观察者比较多的时候,性能问题是比较令人担忧的。并且,在链式结构中,比较容易出现循环引用的错误,造成系统假死。并且因为观察者列表中维护了一份观察者的引用,当它们没有被及时地释放的话,可能会引起内存泄漏。