每日一个设计模式之观察者设计模式

Scroll Down

是什么(what)

观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subcribe Design Pattern)。是23种设计模式之一,属于行为型模式。

概念:

英文:Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and update automatically。
中文:定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。

以微信公众号订阅为背景,介绍观察者模式:

image

如上图所示,服务号就是我们的主题,使用者就是观察者。现在我们明确下功能:

1、服务号就是主题,业务就是推送消息

2、观察者只需要订阅主题,只要有新的消息就会送来

3、当不想要此主题消息时,取消订阅

4、只要服务号还在,就会一直有人订阅

好了,现在我们来看看观察者模式的类图(取自Head First 设计模式一书):

image-1672652053558

怎么用(how)

业务场景

1.上游一来数据,下游就变化

生活场景

1.接入气象站天气数据,天气变化,多种布告板(大屏幕等)立即显示变化(Head First 设计模式例子)

2.少林足球,星爷在台上表演,台下观众根据台上表演做出变化(一群人看着一个东西而做出相应的反应。)

3.猫抓老鼠,老鼠一出来,猫就扑上去

4.烧水的同时看《三国演义》,水烧开,关闭开关,洗脚

现有框架及JDK使用

1.Java

Observer接口和Observable类

EventObject和EventListener

2.Spring

EventObject和EventListener

参考文章:

https://blog.csdn.net/shang_0122/article/details/120679734

代码show

我们模拟一个《逻辑思维》服务号,和一些订阅者。

首先开始写我们的主题接口,和观察者接口:

package cn.wangzengqiang.study.designpattern.observer.own;

/**
 * 主题接口,所有的主题必须实现此接口
 */
public interface Subject {
    /**
     * 注册一个观察者
     *
     * @param observer
     */
    void registerObserver(Observer observer);

    /**
     * 移除一个观察者
     *
     * @param observer
     */
    void removeObserver(Observer observer);

    /**
     * 通知所有的观察着
     */
    void notifyObservers();

}
package cn.wangzengqiang.study.designpattern.observer.own;

/**
 * 所有的观察者需要实现此接口
 */
public interface Observer {
    void update(String msg);
}

接下来《逻辑思维》服务号的实现类:

package cn.wangzengqiang.study.designpattern.observer.own;

import java.util.ArrayList;
import java.util.List;

/**
 * 逻辑思维 订阅号
 */
public class LogicalThinking implements Subject {
    private List<Observer> observers = new ArrayList<Observer>();
    /**
     * 逻辑思维每日60s主题
     */
    private String msg;

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if (i >= 0) {
            observers.remove(i);
        }

    }

    /**
     * 主题更新消息
     */
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(msg);
        }
    }

    public void setMsg(String msg) {
        this.msg = msg;
        notifyObservers();
    }
}

模拟两个使用者:

package cn.wangzengqiang.study.designpattern.observer.own;

/**
 * 订阅者:工一
 */
public class Gongyi implements Observer {
    private Subject subject;

    public Gongyi(Subject subject) {
        this.subject = subject;
        subject.registerObserver(this);
    }

    @Override
    public void update(String msg) {
        System.out.println(getClass().getSimpleName() + ",请您收看罗胖60s:" + msg);
    }
}


package cn.wangzengqiang.study.designpattern.observer.own;

/**
 * 订阅者:木子
 */
public class Muzi implements Observer {
    private Subject subject;

    public Muzi(Subject subject) {
        this.subject = subject;
        subject.registerObserver(this);
    }

    @Override
    public void update(String msg) {
        System.out.println(getClass().getSimpleName() + ",请您收看罗胖60s:" + msg);
    }
}

可以看出:服务号中维护了所有向它订阅消息的使用者,当服务号有新消息时,通知所有的使用者。

整个架构是一种松耦合,主题的实现不依赖与使用者,当增加新的使用者时,主题的代码不需要改变;使用者如何处理得到的数据与主题无关;

最后看下测试代码:

package cn.wangzengqiang.study.designpattern.observer.own;

import org.junit.Test;

public class LogicalThinkingTest {
    @Test
    public void test1() {
        //模拟一个《逻辑思维》的服务号
        LogicalThinking logicalThinking = new LogicalThinking();
        //订阅者:工一
        Observer gongyi = new Gongyi(logicalThinking);
        Observer muzi = new Muzi(logicalThinking);
        //发布消息
        logicalThinking.setMsg("2022.12.20 怎么坚持一件长期的事");
        logicalThinking.setMsg("2022.12.21 10年期满,今日告别");
    }
}

输出结果:

Gongyi,请您收看罗胖60s:2022.12.20 怎么坚持一件长期的事
Muzi,请您收看罗胖60s:2022.12.20 怎么坚持一件长期的事
Gongyi,请您收看罗胖60s:2022.12.21 10年期满,今日告别
Muzi,请您收看罗胖60s:2022.12.21 10年期满,今日告别

类图:

image-1672652082548

恭喜你学会了观察者模式,上面的观察者模式使我们从无到有的写出,当然了java中已经帮我们实现了观察者模式,借助于java.util.Observable和java.util.Observer。

下面我们使用Java内置的类实现观察者模式:【以我订阅的 逻辑思维 和 人民日报 公众号 为例】

首先是一个 逻辑思维 的订阅号主题:

package cn.wangzengqiang.study.designpattern.observer.own.java;

import java.util.Observable;

/**
 * 逻辑思维 订阅号
 */
public class LogicalThinking extends Observable {
    private String msg;

    public String getMsg() {
        return msg;
    }

    /**
     * 主题更新消息
     *
     * @param msg
     */
    public void setMsg(String msg) {
        this.msg = msg;
        setChanged();
        notifyObservers();
    }
}

下面是一个 人民日报 的订阅号主题:

package cn.wangzengqiang.study.designpattern.observer.own.java;

import java.util.Observable;

/**
 * 人民日报 订阅号
 */
public class PeopleDaily extends Observable {
    private String msg;

    public String getMsg() {
        return msg;
    }

    /**
     * 主题更新消息
     *
     * @param msg
     */
    public void setMsg(String msg) {
        this.msg = msg;
        setChanged();
        notifyObservers();
    }
}

最后是我们的使用者:

package cn.wangzengqiang.study.designpattern.observer.own.java;

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

/**
 * 订阅者:工一
 */
public class Gongyi implements Observer {
    public void registerSubject(Observable observable) {
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof LogicalThinking) {
            LogicalThinking logicalThinking = (LogicalThinking) o;
            System.out.println(logicalThinking.getClass().getSimpleName() + "'s msg -- >" + logicalThinking.getMsg());
        }
        if (o instanceof PeopleDaily) {
            PeopleDaily peopleDaily = (PeopleDaily) o;
            System.out.println(peopleDaily.getClass().getSimpleName() + "'s msg -- >" + peopleDaily.getMsg());
        }

    }
}

测试代码:

package cn.wangzengqiang.study.designpattern.observer.own.java;


import org.junit.Test;

public class LogicalThinkingTest {
    /**
     * 测试java内置的观察者模式实现
     */
    @Test
    public void test1() {
        LogicalThinking logicalThinking = new LogicalThinking();
        PeopleDaily peopleDaily = new PeopleDaily();

        Gongyi gongyi = new Gongyi();
        //工一 订阅 逻辑思维,人民日报
        gongyi.registerSubject(logicalThinking);
        gongyi.registerSubject(peopleDaily);

        //发布消息
        logicalThinking.setMsg("20230102 收藏|罗胖60秒十年精华版:那些打开思路的概念");
        peopleDaily.setMsg("20230102 来了!新闻早班车");

    }

}

输出结果:

LogicalThinking's msg -- >20230102 收藏|罗胖60秒十年精华版:那些打开思路的概念
PeopleDaily's msg -- >20230102 来了!新闻早班车

类图:

image-1672652100898

可以看出,使用Java内置的类实现观察者模式,代码非常简洁,对addObserver,removeObserver,notifyObservers都已经为我们实现了,

可以看出Observable(主题)是一个类,而不是一个接口,基本上书上都对于Java的如此设计抱有反面的态度,觉得Java内置的观察者模式,违反了面向接口编程这个原则,但是如果转念想一想,的确你拿一个主题在这写观察者模式(我们自己的实现),接口的思想很好,但是如果现在继续添加很多个主题,每个主题的addObserver,removeObserver,notifyObservers代码基本都是相同的吧,接口是无法实现代码复用(Java8接口默认实现可以)的,而且也没有办法使用组合的模式实现这三个方法的复用,所以我觉得这里把这三个方法在类中实现是合理的。

为什么用(why)

参考设计模式之美的一段总结

回到本质,设计模式要干的事情就是解耦。

创建型模式是将创建对象和使用对象解耦,

结构型模式是将不同功能代码解耦,

行为型模式是将不同的行为代码解耦,

具体到观察者模式,是将观察者和被观察者代码解耦。