zoukankan      html  css  js  c++  java
  • 设计模式之观察者模式

    前言

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

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

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

    观察者模式的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 设计模式》
  • 相关阅读:
    CCF-CSP201512-3 画图
    CCF-CSP201512-2 消除类游戏
    CCF-CSP201606-4 游戏(BFS)
    CCF-CSP201604-2 俄罗斯方块
    HDU1035 Robot Motion(dfs)
    Java Srting之Calendar日历类(五)——Calendar中计算时间的方法add()
    java如何获取当前日期和时间
    double 类型怎样不用科学计数法表示并且使用Java正则表达式去掉Double类型的数据后面多余的0
    @SpringBootApplication(exclude={DataSourceAutoConfiguration.class})注解作用
    java.util.Date.toString()方法实例
  • 原文地址:https://www.cnblogs.com/wei57960/p/12977484.html
Copyright © 2011-2022 走看看