[设计模式] 观察者模式

[设计模式] 观察者模式

手机用户请横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。

平台 地址
CSDN https://blog.csdn.net/sinat_28690417
简书 https://www.jianshu.com/u/3032cc862300
个人博客 https://yiyuery.github.io/NoteBooks/

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

对于观察模式的实现方式有很多种,一般是以包含Subject和Observer接口的类设计做法最为常见。

根据多个观察者对于同一主题信息的获取方式,可以分为:

  1. 被观察者(主题)主动推送,一变化,就将被观察对象推送给所有观察者。
  2. 观察者通过接口主动拉取主题中的部分信息,避免无关信息的干扰。

观察者模式的好处:

  • 提供了一种对象设计,让主题和观察者之间松耦合
  • 解决一对多的依赖问题

场景分析

加入我们现在有个手机工厂,每次生产出一批手机后,就需要通知对应的代理商过来拿货。

我们就可以利用观察者模式来实现这个场景需求。

  • 手机工厂就是被观察对象,也就是主题。
  • 多个代理商分别充当不同的观察者对象。需要监听手机工厂的
    生产情况。
  • 一旦手机工厂生产出手机,就会通知我们的代理商过来取货。

实战

类图

在这里插入图片描述
  • 首先,我们需要定义观察者和被观察者接口,并定义通知能力接口。
public interface Observer<T> {

    /**
     * 观察者接收通知
     */
    void handle(T message);
}

public interface Subject<T> extends INotify{

    /**
     * 注册观察者
     * @param observer
     */
    void register(Observer<T> observer);

    /**
     * 注销观察者
     * @param observer
     */
    void cancel(Observer<T> observer);

}

public interface INotify {

    /**
     * 通知
     */
    void sendMessage();
}

  • 然后,定义对应实例实现观察者和被观察者

手机工厂

public class PhoneFactory implements Subject<Message> {

    private List<Observer<Message>> observers;

    /**
     * 消息体
     */
    private Message message;

    public PhoneFactory() {
        this.observers = new ArrayList<>();
    }

    /**
     * 注册观察者
     *
     * @param observer
     */
    @Override
    public void register(Observer<Message> observer) {
        observers.add(observer);
    }

    /**
     * 注销观察者
     *
     * @param observer
     */
    @Override
    public void cancel(Observer<Message> observer) {
        observers.remove(observer);
    }

    /**
     * 发送通知
     */
    @Override
    public void sendMessage() {
        observers.forEach(p->{
            p.handle(message);
        });
    }

    /**
     * 外部设置消息变更
     * @param message
     */
    public void setMessage(Message message) {
        this.message = message;
        sendMessage();
    }
}

消息体

@Data
@Builder
@AllArgsConstructor
public class Message {

    /**
     * 消息体
     */
    private String data;
}

代理商

//淘宝
public class TaobaoAgent implements Observer<Message> {
    /**
     * 观察者接收通知
     *
     * @param message
     */
    @Override
    public void handle(Message message) {
        System.out.println("Taobao receive message: " + message.getData());
    }
}
//天猫
public class TianmaoAgent implements Observer<Message> {
    /**
     * 观察者接收通知
     *
     * @param message
     */
    @Override
    public void handle(Message message) {
        System.out.println("Tianmao receive message: " + message.getData());
    }
}
  • 最后,定义测试类,我们来看下效果

/**
 * 自定义观察者模式
 */
@Test
public void testX1() {
    //被观察者
    PhoneFactory phoneFactory = new PhoneFactory();
    //观察者
    TianmaoAgent tianmaoAgent = new TianmaoAgent();
    TaobaoAgent taobaoAgent = new TaobaoAgent();
    //注册
    phoneFactory.register(tianmaoAgent);
    phoneFactory.register(taobaoAgent);
    //发送通知
    phoneFactory.setMessage(Message.builder()
            .data("刚生产了100台Xiaomi手机")
            .build());
    //移除观察者
    phoneFactory.cancel(tianmaoAgent);
    //再次发送通知
    System.out.println("-------------------");
    phoneFactory.setMessage(Message.builder()
            .data("刚又生产了100台iPhone")
            .build());

}

打印

Tianmao receive message: 刚生产了100台Xiaomi手机
Taobao receive message: 刚生产了100台Xiaomi手机
-------------------
Taobao receive message: 刚又生产了100台iPhone

从输出可以看出,观察者和被观察者有以下几个特点:

  • 观察者可以随时进行注册和注销行为
  • 被观察者只会给注册过的观察者发送通知
  • 不同代理商接收通知的顺序和自己注册的先后有关系

那么思考下这个模式有没有什么不好的地方?

  • 每次被观察者发送消息,观察者都需要全部接收并处理。如果信息比较复杂或是观察者只关注其中一部分数据怎么处理?
  • 这种属于被动等待被观察者推的行为,如果不是通知,而是其他一些实时数据的监控呢?如果观察者不想等待,想直接获取当前实时数据信息呢?
  • JDK内置了观察者模式的实现,为什么还要自己定义呢?与自定义的有什么区别呢?

带着问题,我们来看下JDK内置的观察者模式怎么实现的?并看下如何实现主动拉取被观察者信息。

JDK内置观察者模式实现

public class PhoneFactory2 extends Observable {

    /**
     * 消息体
     */
    private Message message;


    public void setMessage(Message message) {
        this.message = message;
        setChanged();
        notifyObservers(message);
    }

    /**
     * 观察者按需主动拉取自己所需要信息
     * @return
     */
    public Message getMessage() {
        return message;
    }
}

public class TaobaoAgent2 implements Observer {

    /**
     * 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) {
        System.out.println("Taobao2 receive message: " + arg.toString());
    }
}

public class TianmaoAgent2 implements Observer {

    /**
     * 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) {
        System.out.println("Tianmao2 receive message: " + arg.toString());
    }

}

比较下变化:

  • 首先代码量上减少了,不需要定义接口
  • notifyObservers方法不用实现,直接使用父层方法
  • 使用的是继承的方式实现观察者和被观察者
  • 定义了getMessage方法,可以主动拉取信息
  • 发送通知前,需要调用父层方法setChanged

定义下测试类:

/**
 * 使用JDK原生观察者模式
 */
@Test
public void testX2() {
    //被观察者
    PhoneFactory2 phoneFactory = new PhoneFactory2();
    //观察者
    TianmaoAgent2 tianmaoAgent = new TianmaoAgent2();
    TaobaoAgent2 taobaoAgent = new TaobaoAgent2();
    //注册
    phoneFactory.addObserver(tianmaoAgent);
    phoneFactory.addObserver(taobaoAgent);
    //发送通知
    phoneFactory.setMessage(Message.builder()
            .data("刚生产了100台Xiaomi手机")
            .build());
    //移除观察者
    phoneFactory.deleteObserver(tianmaoAgent);
    //再次发送通知
    System.out.println("-------------------");
    phoneFactory.setMessage(Message.builder()
            .data("刚又生产了100台iPhone")
            .build());
    //主动拉取消息
    System.out.println("pull message:" + phoneFactory.getMessage());
}

看下测试类的输出:

Taobao2 receive message: Message(data=刚生产了100台Xiaomi手机)
Tianmao2 receive message: Message(data=刚生产了100台Xiaomi手机)
-------------------
Taobao2 receive message: Message(data=刚又生产了100台iPhone)
pull message:Message(data=刚又生产了100台iPhone)

分析下两次输出的区别:

  • 接收消息的顺序反了
  • 被观察者开放了自身被监控属性的Getter方法

其实乍看之下,两种方式实现结果貌似没什么区别,但是仔细观察JDK内置的Observable,不是一个接口,是个类。其内部的setChanged方法也是被protected修饰的

protected synchronized void setChanged() {
        changed = true;
}

这意味着:你除非继承自Observable,否则无法创建Observable实例,并组合到你自己的对象中来,这个设计违反了多用组合,少用继承的设计原则。

相较于我们自己实现的观察者模式,可以结合泛型定义被通知的消息体,还可以结合我们的通知接口INotify等方式来扩展,开发自由度更加高。但是无论是哪种方式,关键是熟悉观察者模式后,善用即可,

REFERENCES

《Head First》读书笔记

更多

扫码关注架构探险之道,回复文章标题,获取本文相关源码和资源链接

在这里插入图片描述

知识星球(扫码加入获取历史源码和文章资源链接)

在这里插入图片描述
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容