「补课」进行时:设计模式(15)——观察者模式

共 7973字,需浏览 16分钟

 ·

2020-11-30 10:15

65922c7e4b8aaf7e82e6e8a9a87df3ee.webp

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 通用类图

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

2.3 通用代码

Subject 被观察者:

public abstract class Subject {
    // 定义一个观察者数组
    private Vector 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 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("\n<%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 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("\n<%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("设计模式 | 单例模式及典型应用""单例模式的内容....");
    }
}

测试结果:

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

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

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





感谢阅读d231f4aaabc4692d4cb29c1a182afdf8.webp



浏览 34
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报