zoukankan      html  css  js  c++  java
  • 「补课」进行时:设计模式(15)——观察者模式

    1. 前文汇总

    「补课」进行时:设计模式系列

    2. 观察者模式

    2.1 定义

    观察者模式(Observer Pattern)也叫做发布订阅模式(Publish/subscribe),它是一个在项目中经常使用的模式,其定义如下:

    Define a one-to-many dependency between objects so that when oneobject changes state,all its dependents are notified and updatedautomatically.(定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。)

    2.2 通用类图

    • Subject 被观察者: 定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
    • ConcreteSubject 具体的被观察者: 定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
    • Observer 观察者: 观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。
    • ConcreteObserver 具体的观察者: 每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。

    2.3 通用代码

    Subject 被观察者:

    public abstract class Subject {
        // 定义一个观察者数组
        private Vector<Observer> obsVector = new Vector<>();
        // 添加一个观察者
        public void addObserver(Observer obsVector) {
            this.obsVector.add(obsVector);
        }
        // 删除一个观察者
        public void delObserver(Observer observer) {
            this.obsVector.remove(observer);
        }
        // 通知所有观察者
        public void notifyObservers() {
            for (Observer obs : this.obsVector) {
                obs.update();
            }
        }
    }
    

    ConcreteSubject 具体的被观察者:

    public class ConcreteSubject extends Subject {
        public void doSomething() {
            // 具体的业务
            super.notifyObservers();
        }
    }
    

    Observer 观察者:

    public interface Observer {
        void update();
    }
    

    ConcreteObserver 具体的观察者:

    public class ConcreteObserver implements Observer{
        @Override
        public void update() {
            System.out.println("进行消息处理");
        }
    }
    

    测试场景类:

    public class Test {
        public static void main(String[] args) {
            // 创建一个被观察者
            ConcreteSubject subject = new ConcreteSubject();
            // 创建一个观察者
            Observer observer = new ConcreteObserver();
            // 观察者观察被观察者
            subject.addObserver(observer);
            // 观察者开始活动了
            subject.doSomething();
        }
    }
    

    3. 一个案例

    观察者模式是设计模式中的超级模式,有关他的应用随处可见。

    就比如说微信公众号,我每天推送一篇博文内容,订阅的用户都能够在我发布推送之后及时接收到推送,方便地在手机端进行阅读。

    订阅者接口(观察者)

    public interface Subscriber {
        void receive(String publisher, String articleName);
    }
    

    微信客户端(具体观察者)

    public class WeChatClient implements Subscriber {
    
        private String username;
    
        public WeChatClient(String username) {
            this.username = username;
        }
    
        @Override
        public void receive(String publisher, String articleName) {
            System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, publisher, articleName));
        }
    }
    

    一个微信客户端(具体观察者)

    public class Publisher {
        private List<Subscriber> subscribers;
        private boolean pubStatus = false;
    
        public Publisher() {
            subscribers = new ArrayList<>();
        }
    
        protected void subscribe(Subscriber subscriber) {
            this.subscribers.add(subscriber);
        }
    
        protected void unsubscribe(Subscriber subscriber) {
            if (this.subscribers.contains(subscriber)) {
                this.subscribers.remove(subscriber);
            }
        }
    
        protected void notifySubscribers(String publisher, String articleName) {
            if (this.pubStatus == false) {
                return;
            }
            for (Subscriber subscriber : this.subscribers) {
                subscriber.receive(publisher, articleName);
            }
            this.clearPubStatus();
        }
    
        protected void setPubStatus() {
            this.pubStatus = true;
        }
    
        protected void clearPubStatus() {
            this.pubStatus = false;
        }
    }
    

    具体目标

    public class WeChatAccounts extends Publisher {
        private String name;
    
        public WeChatAccounts(String name) {
            this.name = name;
        }
    
        public void publishArticles(String articleName, String content) {
            System.out.println(String.format("
    <%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
            setPubStatus();
            notifySubscribers(this.name, articleName);
        }
    }
    

    测试类

    public class Test {
        public static void main(String[] args) {
            WeChatAccounts accounts = new WeChatAccounts("极客挖掘机");
    
            WeChatClient user1 = new WeChatClient("张三");
            WeChatClient user2 = new WeChatClient("李四");
            WeChatClient user3 = new WeChatClient("王五");
    
            accounts.subscribe(user1);
            accounts.subscribe(user2);
            accounts.subscribe(user3);
    
            accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");
    
            accounts.unsubscribe(user1);
            accounts.publishArticles("设计模式 | 单例模式及典型应用", "单例模式的内容....");
        }
    }
    

    测试结果

    <极客挖掘机>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 观察者模式及典型应用>,内容为 <观察者模式的内容...> 
    用户<张三> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
    用户<李四> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
    用户<王五> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
    
    <极客挖掘机>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 单例模式及典型应用>,内容为 <单例模式的内容....> 
    用户<李四> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>
    用户<王五> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>
    

    4. JDK 对的观察者模式的支持

    观察者模式在 Java 语言中的地位非常重要。在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了JDK对观察者模式的支持。

    java.util.Observer 接口中,仅有一个 update(Observable o, Object arg) 方法,当观察目标发生变化时被调用:

    public interface Observer {
        void update(Observable o, Object arg);
    }
    

    java.util.Observable 类则为目标类:

    public class Observable {
        private boolean changed = false;
        private Vector<Observer> obs;
    
        public Observable() {
            obs = new Vector<>();
        }
        // 用于注册新的观察者对象到向量中
        public synchronized void addObserver(Observer o) {
            if (o == null)
                throw new NullPointerException();
            if (!obs.contains(o)) {
                obs.addElement(o);
            }
        }
        // 用于删除向量中的某一个观察者对象
        public synchronized void deleteObserver(Observer o) {
            obs.removeElement(o);
        }
    
        public void notifyObservers() {
            notifyObservers(null);
        }
        // 通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法
        public void notifyObservers(Object arg) {
            Object[] arrLocal;
    
            synchronized (this) {
                if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
    
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(this, arg);
        }
        // 用于清空向量,即删除向量中所有观察者对象
        public synchronized void deleteObservers() {
            obs.removeAllElements();
        }
        // 该方法被调用后会设置一个boolean类型的内部标记变量changed的值为true,表示观察目标对象的状态发生了变化
        protected synchronized void setChanged() {
            changed = true;
        }
        // 用于将changed变量的值设为false,表示对象状态不再发生改变或者已经通知了所有的观察者对象,调用了它们的update()方法
        protected synchronized void clearChanged() {
            changed = false;
        }
        // 返回对象状态是否改变
        public synchronized boolean hasChanged() {
            return changed;
        }
        // 返回向量中观察者的数量
        public synchronized int countObservers() {
            return obs.size();
        }
    }
    

    相比较我们自己的示例 Publisherjava.util.Observer 中多了并发和 NPE 方面的考虑 。

    使用 JDK 对观察者模式的支持,改写一下上面的示例:

    增加一个通知类 WechatNotice ,用于推送通知的传递:

    public class WechatNotice {
        private String publisher;
        private String articleName;
        // 省略 get/set
    }
    

    然后改写 WeChatClientWeChatAccounts ,分别实现 JDK 的 Observer 接口和继承 Observable 类:

    public class WeChatClient implements Observer {
    
        private String username;
    
        public WeChatClient(String username) {
            this.username = username;
        }
    
        @Override
        public void update(Observable o, Object arg) {
            WechatNotice notice = (WechatNotice) arg;
            System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName()));
        }
    }
    
    public class WeChatAccounts extends Observable {
        private String name;
    
        public WeChatAccounts(String name) {
            this.name = name;
        }
    
        public void publishArticles(String articleName, String content) {
            System.out.println(String.format("
    <%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
            setChanged();
            notifyObservers(new WechatNotice(this.name, articleName));
        }
    }
    

    最后是一个测试类:

    public class Test {
        public static void main(String[] args) {
            WeChatAccounts accounts = new WeChatAccounts("极客挖掘机");
    
            WeChatClient user1 = new WeChatClient("张三");
            WeChatClient user2 = new WeChatClient("李四");
            WeChatClient user3 = new WeChatClient("王五");
    
            accounts.addObserver(user1);
            accounts.addObserver(user2);
            accounts.addObserver(user3);
    
            accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");
    
            accounts.deleteObserver(user1);
            accounts.publishArticles("设计模式 | 单例模式及典型应用", "单例模式的内容....");
        }
    }
    

    测试结果:

    <极客挖掘机>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 观察者模式及典型应用>,内容为 <观察者模式的内容...> 
    用户<王五> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
    用户<李四> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
    用户<张三> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
    
    <极客挖掘机>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 单例模式及典型应用>,内容为 <单例模式的内容....> 
    用户<王五> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>
    用户<李四> 接收到 <极客挖掘机>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>
    

    和前面的示例结果完全一致。

  • 相关阅读:
    Uboot USB模式(RK3288变砖头的解决办法)
    C++ 解析一
    C++基础
    shell脚本分析二
    ASCII
    POJ 1970 The Game (DFS)
    PoJ1979 Red and Black (DFS)
    UVA 572 Oil Deposits (DFS)
    打印日历
    求第N个素数
  • 原文地址:https://www.cnblogs.com/babycomeon/p/14059433.html
Copyright © 2011-2022 走看看