当前位置 博文首页 > 韩超的博客 (hanchao5272):设计模式-观察者模式-以报纸订阅为例

    韩超的博客 (hanchao5272):设计模式-观察者模式-以报纸订阅为例

    作者:[db:作者] 时间:2021-09-05 16:13

    超级链接: Java常用设计模式的实例学习系列-绪论

    参考:《HeadFirst设计模式》


    1.关于观察者模式

    观察者模式是一种行为模式。

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

    本文以报纸订阅为场景来学习观察者模式

    • 用户可以订阅报纸,也可以取消订阅报纸。
    • 可以将报纸发布给所有订阅它的用户。

    2.原始实现

    不考虑观察者模式,我们针对这个场景进行分析:

    • 因为可以将报纸[Newspaper]发布给所有订阅它的用户[User],所以它需要知道有哪些用户订阅了它,所以:
      • 报纸[Newspaper]需要存储已经订阅它的用户集合[Set<User>]
      • 报纸[Newspaper]需要提供方法给所有订阅者邮寄报纸[mailNewspaperToSubscribers()]
      • 用户[User]需要提供方法用于接受报纸[receiveNewspaper()]
    • 因为用户可以订阅报纸,也可以取消订阅报纸,所以可以理解为:报纸可以记录谁订阅了它,也可以知道谁取消了订阅
      • 报纸[Newspaper]需要提供方法用于记录谁订阅了它[subscribe()]
      • 报纸[Newspaper]需要提供方法用于记录谁取消了订阅[cancel()]

    根据上述分析进行编程。

    用户:User

    用户[User]需要提供方法用于接受报纸[receiveNewspaper()]

    /**
     * <p>用户</P>
     *
     * @author hanchao
     */
    @AllArgsConstructor
    @ToString
    @EqualsAndHashCode
    @Slf4j
    public class User {
        /**
         * 用户编号
         */
        @Setter
        @Getter
        private Integer id;
        /**
         * 用户名称
         */
        @Setter
        @Getter
        private String name;
    
        /**
         * 签收报纸
         */
        public void receiveNewspaper(String message) {
            log.info("{} Received Newspaper [{}].", name, message);
        }
    }
    

    报纸:Newspaper

    • 报纸[Newspaper]需要存储已经订阅它的用户集合[Set<User>]
    • 报纸[Newspaper]需要提供方法给所有订阅者邮寄报纸[mailNewspaperToSubscribers()]
    • 报纸[Newspaper]需要提供方法用于记录谁订阅了它[subscribe()]
    • 报纸[Newspaper]需要提供方法用于记录谁取消了订阅[cancel()]
    /**
     * <p>报纸</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class Newspaper {
        /**
         * 订阅者列表
         */
        private Set<User> userSet;
    
        /**
         * 报纸信息
         */
        @Setter
        private String message;
    
        public Newspaper() {
            userSet = new HashSet<>();
        }
    
        /**
         * 订阅
         */
        public void subscribe(User user) {
            userSet.add(user);
        }
    
        /**
         * 取消订阅
         */
        public void cancel(User user) {
            userSet.remove(user);
        }
    
        /**
         * 向所有订阅者邮寄报纸
         */
        public void mailNewspaperToSubscribers() {
            userSet.forEach(user -> user.receiveNewspaper(message));
        }
    }
    

    测试代码

        public static void main(String[] args) {
            //开始有2个人订阅报纸
            Newspaper newspaper = new Newspaper();
            newspaper.subscribe(new User(1, "Lily"));
            User jack = new User(2, "Jack");
            newspaper.subscribe(jack);
    
            //邮寄报纸
            newspaper.setMessage("中国青年报");
            newspaper.mailNewspaperToSubscribers();
            System.out.println("--------------------------------------");
    
            //后来,有一个人取消了订阅报纸
            newspaper.cancel(jack);
            //邮寄报纸
            newspaper.setMessage("环球时报");
            newspaper.mailNewspaperToSubscribers();
        }
    

    测试结果:

    2019-07-24 17:51:17,520  INFO - Lily Received Newspaper [中国青年报]. 
    2019-07-24 17:51:17,522  INFO - Jack Received Newspaper [中国青年报]. 
    --------------------------------------
    2019-07-24 17:51:17,522  INFO - Lily Received Newspaper [环球时报]. 
    

    缺点:

    • 未遵从依赖倒置原则(面向接口的编程)

    3.观察者模式实现

    其实第2章的原始实现就是观察者模式的雏形,只不过未遵从依赖倒置原则(面向接口的编程)

    为了统一规范,提高普适性,约定俗成的,在观察者模式中,我们常用观察者Observer主题Subject消息更新update()注册register()取消注册remove()通知观察者notifyObservers()等称呼。

    下面按照观察者模式的约定,进行一遍实现。

    • 用户[User]抽象出接口观察者[Observer]
    • 报纸[Newspaper]抽象出接口主题[Subject]

    观察者抽象:Observer

    /**
     * <p>观察者</P>
     *
     * @author hanchao
     */
    public interface Observer {
        /**
         * 消息更新
         */
        void update(String message);
    }
    

    观察者实现:User

    /**
     * <p>用户</P>
     *
     * @author hanchao
     */
    @AllArgsConstructor
    @Slf4j
    @ToString
    @EqualsAndHashCode
    public class User  implements Observer{
        /**
         * 用户编号
         */
        @Setter
        @Getter
        private Integer id;
        /**
         * 用户名称
         */
        @Setter
        @Getter
        private String name;
    
        /**
         * 签收报纸
         */
        @Override
        public void update(String message) {
            log.info("{} Received Newspaper [{}].", name, message);
        }
    }
    

    主题抽象:Subject

    /**
     * <p>主题</P>
     *
     * @author hanchao
     */
    public interface Subject {
        /**
         * 注册
         */
        void register(Observer observer);
    
        /**
         * 取消注册
         */
        void remove(Observer observer);
    
        /**
         * 通知观察者们
         */
        void notifyObservers();
    }
    

    主题实现:Newspaper

    /**
     * <p>报纸</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class Newspaper implements Subject {
        /**
         * 订阅者列表
         */
        private Set<Observer> observerSet;
    
        /**
         * 报纸信息
         */
        @Setter
        private String message;
    
        public Newspaper() {
            observerSet = new HashSet<>();
        }
    
        /**
         * 订阅
         *
         * @param observer 订阅者
         */
        @Override
        public void register(Observer observer) {
            observerSet.add(observer);
        }
    
        /**
         * 取消订阅
         *
         * @param observer 订阅者
         */
        @Override
        public void remove(Observer observer) {
            observerSet.remove(observer);
        }
    
        /**
         * 向所有订阅者邮寄报纸
         */
        @Override
        public void notifyObservers() {
            observerSet.forEach(user -> user.update(message));
        }
    }
    

    测试代码:

        public static void main(String[] args) {
            //开始有2个人订阅报纸
            Newspaper newspaper = new Newspaper();
            newspaper.register(new User(1, "Lily"));
            User jack = new User(2, "Jack");
            newspaper.register(jack);
    
            //邮寄报纸
            newspaper.setMessage("中国青年报");
            newspaper.notifyObservers();
            System.out.println("--------------------------------------");
    
            //后来,有一个人取消了订阅报纸
            newspaper.remove(jack);
            //邮寄报纸
            newspaper.setMessage("环球时报");
            newspaper.notifyObservers();
        }
    

    4.Java内置观察者模式

    Java本身提供了内置的观察者模式,位于rt.jar包中:

    • 其观察者是Observer接口
    • 主题是被观察者Observable类,注意,这是一个类。

    下面针对报纸订阅场景进行内置观察者实现:

    • 用户[User]实现父接口观察者[Observer]
    • 报纸[Newspaper]继承父类被观察者[Observable]

    观察者实现:User

    /**
     * <p>用户</P>
     *
     * @author hanchao
     */
    @AllArgsConstructor
    @Slf4j
    @ToString
    @EqualsAndHashCode
    public class User implements Observer {
        /**
         * 用户编号
         */
        @Setter
        @Getter
        private Integer id;
        /**
         * 用户名称
         */
        @Setter
        @Getter
        private String name;
    
        /**
         * 签收报纸
         * This method is called whenever the observed object is changed. An
         * application calls an <tt>Observable</tt> object's
         * <code>notifyObservers</code> method to have all the object's
         * observers notified of the change.
         *
         * @param o   the observable object.
         * @param arg an argument passed to the <code>notifyObservers</code>
         */
        @Override
        public void update(Observable o, Object arg) {
            log.info("{} Received Newspaper [{}].", name, arg.toString());
        }
    }
    

    被观察者实现:Newspaper

    /**
     * <p>报纸</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class Newspaper extends Observable {
    
        /**
         * 报纸信息
         */
        @Setter
        private String message;
    
        /**
         * 订阅
         * Adds an observer to the set of observers for this object, provided
         * that it is not the same as some observer already in the set.
         * The order in which notifications will be delivered to multiple
         * observers is not specified. See the class comment.
         *
         * @param o an observer to be added.
         * @throws NullPointerException if the parameter o is null.
         */
        @Override
        public synchronized void addObserver(Observer o) {
            super.addObserver(o);
        }
    
        /**
         * 取消订阅
         * Deletes an observer from the set of observers of this object.
         * Passing <CODE>null</CODE> to this method will have no effect.
         *
         * @param o the observer to be deleted.
         */
        @Override
        public synchronized void deleteObserver(Observer o) {
            super.deleteObserver(