设计模式之观察者模式

前言

生活中我们从牛奶厂家订阅了牛奶后,会有快递员在每天早晨给所有订阅牛奶的家庭送牛奶来。如果我们退订了之后,我们之后也不会收到牛奶。观察者模式就类似这样的一个场景,可以把牛奶场景定义为主题,客户理解为观察者。

除了主题主动的"推送"数据给观察者,观察者能否从主题中主动的 "拉取" 数据呢,事实上也是可以做到的。

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

观察者模式的UML

观察者UML

代码实现

// 主题
public interface Subject {
    // 观察者注册
    void register(Observer observer);
    // 观察者退订
    void remove(Observer observer);
    // 通知所有观察者
    void notifyObservers();

}
// 观察者
public interface Observer {
    // 观察者收到数据后进行业务逻辑处理的统一接口
    void update(Object data);
}

// 具体主题
public class ConcreteSubject implements Subject {
    // 维护一个观察者数组
    private List<Observer> observers = new ArrayList<>();

    private Object data;

    @Override
    public void register(Observer observer) {
        if (!observers.contains(observer)) {
            observers.add(observer);
        }
    }

    @Override
    public void remove(Observer observer) {
        if (observers.contains(observer)) {
            observers.remove(observer);
        }
    }

    // 具体逻辑实现,逐个调用观察者update() 方法
    @Override
    public void notifyObservers() {
        for (int i = 0; i < observers.size(); i++) {
            observers.get(i).update(data);
        }
    }

    public void setData(Object data) {
        this.data = data;
        // 数据改变时通知观察者
        notifyObservers();
    }

}
// 具体观察者
public class ConcreteObserver implements Observer {
    // 维护一个主题 便于退订
    private Subject subject;

    public ConcreteObserver(Subject subject) {
        this.subject = subject;
        subject.register(this);
    }

    // 退订
    public void remove() {
        subject.remove(this);
    }

    @Override
    public void update(Object data) {
        System.out.println("i am concrete observer update [ " + data + " ] now");
    }
}
public class Test {

    public static void main(String[] args) {
        ConcreteSubject concreteSubject = new ConcreteSubject();
        ConcreteObserver concreteObserver = new ConcreteObserver(concreteSubject);

        concreteSubject.setData("1");
        concreteSubject.setData("2");
        
        // 退订后不再收到通知
        concreteObserver.remove();
        concreteSubject.setData("11");
    }
}

// 输出:
i am concrete observer update [ 1 ] now
i am concrete observer update [ 2 ] now

JDK 中的观察者模式

JDK 中对于观察者模式的实现让观察者可以主动拉取一些数据,不仅是主题的推送。主要通过Observable (可被观察的)类和 Observer 接口实现。

使用方式: 我们对上面写的代码用 JDK 里面的方式进行改造 然后在代码注释中进行注释讲解使用。

import java.util.Observable;

// 实际主题通过继承 Observable 的方式
public class JDKConcreteSubject extends Observable {

    // 不再管理 Observer 们,超类Observable已经帮我们做了

    // 要传输的数据
    private Object data;

    public void setData(Object data) {
        this.data = data;
        // 想要通知所有的观察者们前需要调用 setChanged() 方法。
        // setChanged() 方法可以让我们更灵活的通知观察者们,观察者们不用那么敏感的感受到所有的数据。
        // 比如我们数据减少了 5% 不想通知观察者,在减少 20% 时再通知观察者们
        setChanged();
        // 通知所有观察者们
        notifyObservers(data);
    }

    // 用于观察者们拉取数据 当然 数据粒度可以控制
    public Object getData(){
        return this.data;
    }

}
import java.util.Observable;
import java.util.Observer;

// 具体观察者实现 Observer 接口
public class JDKConcreteObserver implements Observer {
    // 维护 JDK 方式的主题
    Observable observable;

    public JDKConcreteObserver(Observable observable) {
        this.observable = observable;
        // 订阅
        observable.addObserver(this);
    }

    /**
     * @param o   主题 可用于让观察者知道是哪一个主题来通知的
     * @param arg notifyObservers(Object arg) 中的数据对象
     */
    @Override
    public void update(Observable o, Object arg) {
        // 判断是哪一个主题通知
        if (o instanceof JDKConcreteSubject) {
            JDKConcreteSubject jdkConcreteSubject = (JDKConcreteSubject) o;
            Object data = jdkConcreteSubject.getData();
            System.out.println("get data from subject " + data);
        }
        System.out.println("get data from arg " + arg);
    }
}

JDK 中的观察者模式需要注意的点:

  • 通过继承的方式,限制了复用的潜力
  • 不能依赖于观察者被通知的次序

    (通知顺序不依赖于注册的顺序),注册顺序不能影响通知顺序

References

  • 《Head First 设计模式》
原文地址:https://www.cnblogs.com/wei57960/p/12977484.html