Java设计模式系列之——观察者模式

时间:公元前716年前后(西周)

地点:镐京骊山烽火台

典故:烽火戏诸侯,褒姒一笑失天下

事件:周宣王死后,其子宫涅继位,是为周幽王。适逢关中一带发生地震,加以连年旱灾,百姓饥寒交迫,民不聊生。然周幽王是个荒淫无道的昏君,整日纵情于声色犬马之中,醉生梦死。有大丞褒珦劝谏,反受牢狱之灾三年。褒珦族人为救褒珦,进献美女褒姒。话说这褒姒,可谓名不虚传的绝色佳女。细柳腰,冰肌玉肤、花颜妖娆、色眼微动,便可令人魂不守舍。但褒姒虽生沉鱼落雁闭月羞花之貌,却冷若冰霜,自进宫以来从未笑过一次。这可急的周幽王啊,下令谁能博美人一笑必有重赏。佞臣虢石父献计,立褒珦之子为太子,并于骊山烽火台点燃烽火,让王妃看千军万马壮观场面,必笑颜常开。昏君周幽王听后即令诏书废申皇后和太子宜臼,立褒姒为皇后,伯服为太子,即驾幸骊山。幽王来到骊山温泉行宫,命令点燃烽火,但见狼烟四起,火光冲天,各路诸侯看见烽火,急忙调动三军,直奔骊山。近前却听楼阁里,琴瑟声声,觥筹交错,却不见一兵一卒,可此时的褒姒看见惊恐万状的各路诸侯,却嫣然一笑,幽王见褒姒妩媚万千,不觉欣喜若狂,于是重赏虢石父。

结果:幽王每次点燃烽火都没有事,时间久了诸侯就不再派兵了,最终犬戎兵攻破镐京,于骊山杀幽王,西周灭亡。(此典故今被证明是杜撰)

时间:1980年代某月某日

地点:某乡某村

事件:傍晚4点,村口大广播响起了乡广播站播音员小芳的声音:XX村XX生产队,XX村XX生产队,现通知你们晚饭后7点半在村口场地上召开临时会议,关于生产队秋收问题,请大家务必准时参加。

结果:晚上7点20,村场地上已经聚满了人,大人端着小板凳坐着闲聊等开会,小孩在场地上追逐打闹,猫儿叫狗儿跳,场地上一片喧嚣。

时间:2019年随时

地点:随地

事件:在我们的微信里,每个人都或多或少关注一些订阅号,每天只要这些订阅号发表文章,我们都会第一时间收到推送。类似的还有你的微博关注里,只要你接收通知,那么你关注的艺人或者亲友只要发表动态,你也能随时浏览到。

结果:不管订阅号还是微博,觉得内容吸引你就关注,就会收到推送。觉得不好看就取消关注,就不再收到推送。

上面的三个小故事,是古人、父辈和我们这一代人的生活截然不同的写照,但是不管哪个时代,我们获取消息的方式都有那么一些异曲同工之处,是智慧的延续,也是科技的进步。无论是烽火台、村广播、订阅号还是微博,都有着消息通知者的角色,而各诸侯、父辈们、我们都有着观察者的角色,我们根据收到的通知做不同的事情。接下来就进入我们今天的主题,Java设计模式之——观察者模式。

什么是观察者模式

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

观察者模式(Observer Pattern):在对象之间定义一对多依赖关系,以便当一个对象更改状态时,将自动通知和更新其所有依赖关系。

类似上面的故事里,烽火台和诸侯,广播和村民,订阅号和关注者,都是一对多的依赖关系,一改变并通知给多,多收到通知会做出相应动作,这就是观察者模式的核心。观察者模式属于行为型模式,在项目中也经常使用到。

观察者模式四个角色

本质上观察者模式只有主题(Subject)和观察者(Observer)两个角色,但是在实际使用中我们通常会抽象出四个角色,分别是:

  • 抽象主题(Subject):又叫目标,是被观察对象,包含所有观察者对象的引用,通常是接口或者抽象类,提供动态的增加、移除观察者的方法,同时定义了状态改变时通知所有观察者的通知方法。

  • 具体主题(ConcreteSubject):保存具体发生改变的状态数据,在内部状态改变时,给所有的注册的观察者发出通知。(具体主题实现了抽象主题中定义的抽象业务逻辑方法,如果无需实现,具体主题类可以省略。)

  • 抽象观察者(Observer):定义观察者在收到主题状态改变的通知时的一个更新方法,通常是一个接口。

  • 具体观察者(ConcreteObserver):实现抽象观察者接口,每个具体观察者收到通知后,根据自己不同的业务逻辑去更新自己。

观察者模式UML图

代码示例前言(女神初现)

当开头想到烽火台和父辈们那个年代的时候,记忆的缺口越开越大,那些美好的,悲伤的回忆都涌上心头,索性这篇就卖一下情怀。看到这儿的朋友,如果你还是单身,并且有了想追求的对象,那么你的福利来了,因为我忍着痛回忆我当初是怎么和我的女神擦肩而过的,眼看着她投进别人的怀抱,并将这惨痛的教训写出来,通过我的前车之鉴教你怎样追女神。

那一年,日仅仅代表太阳;那一年,菊花还只是一种花;那一年,肥皂只代表用来洗澡;那一年,人们还敢单纯的向往青青草原;那一年,小鹿还没有去做头发;那一年......那一年甚至还没有微信,那一年我们用的最多的通讯工具是QQ,那一年,我的女神出现了,于千万万人之中,只看她一眼,这世上便再无其他女子。

千方百计以同学的名义加到了她的QQ,随即设置了特别关注。Q的特别关注大家应该都知道吧,就是只要她上线、发表心情、发表日至、发表照片等,我都能第一时间收到通知,于是,我,这个世界的第一条舔狗,人称舔狗鼻祖,就此诞生。

下面忍痛挥泪带大家一起回顾我当年的舔狗生涯吧。

代码实例

1、编写抽象主题角色接口(qq的特别关注功能)

package com.mazhichu.designpatterns.observer;/** * <p class="detail"> * 功能: 特别关注主题接口(抽象主题角色) * </p> * * @author Moore * @ClassName Special attention subject. * @Version V1.0. * @date 2019.11.01 15:37:21 */public interface SpecialAttentionSubject {    /**     * <p class="detail">     * 功能: 添加观察者     * </p>     *     * @param followerObserver :     * @author Moore     * @date 2019.11.01 15:37:21     */    public void addFollower(FollowerObserver followerObserver);    /**     * <p class="detail">     * 功能: 删除观察者     * </p>     *     * @param followerObserver :     * @author Moore     * @date 2019.11.01 15:37:21     */    public void removeFollower(FollowerObserver followerObserver);    /**     * <p class="detail">     * 功能: 通知观察者     * </p>     *     * @author Moore     * @date 2019.11.01 15:37:21     */    public void notifyFollowers();}

2、编写具体主题角色类(我把女神设置为特别关注后,qq会将我加到女神的追随者列表中,女神一有动态,就会通知我)

package com.mazhichu.designpatterns.observer;import java.util.ArrayList;import java.util.List;/** * <p class="detail"> * 功能:女神的特别关注主题(具体主题角色) * </p> * * @author Moore * @ClassName Goddess special attention subject. * @Version V1.0. * @date 2019.11.01 15:32:46 */public class GoddessSpecialAttentionSubject implements SpecialAttentionSubject{    // 包含抽象观察者的集合    private List<FollowerObserver> followerObservers;    private String content;    public GoddessSpecialAttentionSubject() {        this.followerObservers = new ArrayList<FollowerObserver>();    }    /**     * <p class="detail">     * 功能: 添加观察者     * </p>     *     * @param followerObserver :     * @author Moore     * @date 2019.11.01 15:37:21     */    @Override    public void addFollower(FollowerObserver followerObserver) {        followerObservers.add(followerObserver);    }    /**     * <p class="detail">     * 功能: 删除观察者     * </p>     *     * @param followerObserver :     * @author Moore     * @date 2019.11.01 15:37:21     */    @Override    public void removeFollower(FollowerObserver followerObserver) {        followerObservers.remove(followerObserver);    }    /**     * <p class="detail">     * 功能: 通知观察者     * </p>     *     * @author Moore     * @date 2019.11.01 15:37:21     */    @Override    public void notifyFollowers() {        followerObservers.forEach(follower -> follower.update(content));    }    /**     * 女神发表动态以后,通知给所有把女神设置成特别关注的人     * @param content     */    public void sendDynamic(String content){        this.content = content;        notifyFollowers();    }}

3、追女神接口类(这个类与观察者模式无关,只为将我悲惨的故事讲的完整一些,让大家记忆更深刻)

package com.mazhichu.designpatterns.observer;/** * <p class="detail"> * 功能:追女神接口(和观察者模式无关,只为丰富故事完整性) * </p> * * @author Moore * @ClassName Woo goddess. * @Version V1.0. * @date 2019.11.01 15:58:50 */public interface WooGoddess {    public void woo();}

4、编写抽象观察者接口(当收到女神动态时,将女神设为特别关注的人会做什么呢)

package com.mazhichu.designpatterns.observer;/** * <p class="detail"> * 功能: 关注者的观察者接口(抽象观察者角色) * </p> * * @author Moore * @ClassName Follower observer. * @Version V1.0. * @date 2019.11.01 15:18:50 */public interface FollowerObserver {    /**     * <p class="detail">     * 功能: 收到通知后,更新     * </p>     *     * @param content :     * @author Moore     * @date 2019.11.01 15:20:16     */    public void update(String content);    /**     * <p class="detail">     * 功能: 主动添加特别关注     * </p>     *     * @param subject :     * @author Moore     * @date 2019.11.01 15:21:45     */    public void addSpecialAttention(SpecialAttentionSubject subject);}

5、编写具体观察者角色类(我,舔狗鼻祖上线)

​​​​

package com.mazhichu.designpatterns.observer;/** * <p class="detail"> * 功能: 我这条舔狗(具体观察者角色) * </p> * * @author Moore * @ClassName My observer. * @Version V1.0. * @date 2019.11.01 16:10:25 */public class MyObserver implements FollowerObserver,WooGoddess {    private String content;    /**     * <p class="detail">     * 功能: 收到通知后,更新     * </p>     *     * @param content :     * @author Moore     * @date 2019.11.01 15:20:16     */    @Override    public void update(String content) {        this.content = content;        woo();    }    /**     * <p class="detail">     * 功能: 主动添加特别关注     * </p>     *     * @param subject :     * @author Moore     * @date 2019.11.01 15:21:45     */    @Override    public void addSpecialAttention(SpecialAttentionSubject subject) {        subject.addFollower(this);    }    @Override    public void woo() {        System.out.println("我收到女神最新动态:"+content);        if(content.contains("感冒")){            System.out.println("我给女神评论:你要多喝开水,照顾好自己哦!");        }        if(content.contains("生日")){            System.out.println("我给女神评论:生日快乐!永远17岁!女神给我回复了谢谢并发了一张笑脸,我激动了一夜没睡!");        }        if(content.contains("离开")){            System.out.println("我给女神留言:无论你走到哪里,都祝福你一切都好!从此没有收到女神的信息!");        }    }}

5、编写具体观察者角色(说起来这个就要心痛,当时有太多情敌了,但是只有这个情敌让我有危机感)

package com.mazhichu.designpatterns.observer;/** * <p class="detail"> * 功能: 情敌(具体观察者角色) * </p> * * @author Moore * @ClassName Rival in love observer. * @Version V1.0. * @date 2019.11.01 16:11:02 */public class RivalInLoveObserver implements FollowerObserver,WooGoddess {    private String content;    /**     * <p class="detail">     * 功能: 收到通知后,更新     * </p>     *     * @param content :     * @author Moore     * @date 2019.11.01 15:20:16     */    @Override    public void update(String content) {        this.content = content;        woo();    }    /**     * <p class="detail">     * 功能: 主动添加特别关注     * </p>     *     * @param subject :     * @author Moore     * @date 2019.11.01 15:21:45     */    @Override    public void addSpecialAttention(SpecialAttentionSubject subject) {        subject.addFollower(this);    }    @Override    public void woo() {        System.out.println("情敌收到女神最新动态:"+content);        if(content.contains("感冒")){            System.out.println("情敌给女神送去了感冒药,并嘱咐好好休养,女神很感动!");        }        if(content.contains("生日")){            System.out.println("情敌买了鲜花和蛋糕,还送了礼物,是女神最爱的周杰伦演唱会门票,两个人一起去看了!");        }        if(content.contains("离开")){            System.out.println("情敌去了女神的城市,三个月后,情敌和女神在一起了!");        }    }}

6、测试类(在自己的伤口上亲自撒盐,让我带你们回顾我当年是怎样与女神擦肩而过的过程)

package com.mazhichu.designpatterns.observer;public class ObserverStory {    public static void main(String[] args) {        GoddessSpecialAttentionSubject subject = new GoddessSpecialAttentionSubject();        FollowerObserver myObserver = new MyObserver();        myObserver.addSpecialAttention(subject);        FollowerObserver rivalInLoveObserver = new RivalInLoveObserver();        rivalInLoveObserver.addSpecialAttention(subject);        subject.sendDynamic("天气转凉了,有点小感冒");        System.out.println("---------------------\n");        subject.sendDynamic("听刘德华的17岁,祝自己17岁生日快乐");        System.out.println("---------------------\n");        subject.sendDynamic("毕业季,即将离开这座城市,下一站,杭州!");    }}

7、男人哭吧哭吧不是罪,看看最终的结局:

以上就是我的血泪史,也是一本教科书的追女孩子秘籍。舔狗舔狗,舔到最后一无所有,通过这次打击,让我明白喜欢一个女孩子就要大胆勇敢的去表白去追,男人一定要自信点,主动点,机会要自己去创造,这样才有可能成为人生赢家。

同时,这也是一个典型的观察者模式,我和情敌是观察者(Observer),我们的特别关注女神是被观察者(Subject),女神每次状态改变(发动态)都会通知我们,我和情敌就会根据女神动态改变我们自身,虽然我输的彻底,但也算输的明白了。

观察者模式的两种模型

观察者模式又分为推模型和拉模型。

  • 推模型:顾名思义,就是Subject主动将变更的状态和数据全部推送给观察者,而不管观察者是否需要。

  • 拉模型:观察者持有主题对象的引用,也就是Subject把自身作为参数传递给观察者,观察者可以根据自己需要获取数据,而不用获取主题的所有状态和数据。

前面我与女神的故事(其实是我一厢情愿)就是推模型,只要女神更新了动态,不管我想不想看,Q都会主动推送给我。那么拉模型呢?不想伤口一次次被撕开,所以,拉模型,我用另一个例子来简单说说。很多朋友都下载了腾讯新闻、虎扑、网易、头条这样的app,应该都记得首次登录的时候会让用户设置关注的模块或者内容什么的,如果你设置了,就会为你单独展示该模块,每次点击模块就会获取相应内容,如果你不想看,就不用点击推荐模块。这就是典型的拉模型,按需获取。下面来看看代码实例。

拉模型代码示例

1、还是编写抽象主题角色

package com.mazhichu.designpatterns.observer;/** * <p class="detail"> * 功能: 抽象主题角色 * </p> * * @author Moore * @ClassName Subject. * @Version V1.0. * @date 2019.11.04 10:42:57 */public interface Subject {    /**     * <p class="detail">     * 功能: 增加观察者对象     * </p>     *     * @param observer :     * @author Moore     * @date 2019.11.04 10:42:57     */    public void add(Observer observer);    /**     * <p class="detail">     * 功能: 删除观察者对象     * </p>     *     * @param observer :     * @author Moore     * @date 2019.11.04 10:42:57     */    public void remove(Observer observer);    /**     * <p class="detail">     * 功能: 通知观察者     * </p>     *     * @author Moore     * @date 2019.11.04 10:42:58     */    public void notifyObserver();}

2、编写具体主题类

package com.mazhichu.designpatterns.observer;import lombok.Getter;import java.util.ArrayList;import java.util.List;/** * <p class="detail"> * 功能: 具体主题类 * </p> * * @author Moore * @ClassName Concrete subject. * @Version V1.0. * @date 2019.11.04 13:59:11 */public class ConcreteSubject implements Subject {    // 包含抽象观察者的集合    private List<Observer> observers;    @Getter    private String basketball;    @Getter    private String football;    @Getter    private String showbiz;
    public ConcreteSubject() {        this.observers = new ArrayList<Observer>();    }    /**     * <p class="detail">     * 功能: 增加观察者对象     * </p>     *     * @param observer :     * @author Moore     * @date 2019.11.04 10:42:57     */    @Override    public void add(Observer observer) {        observers.add(observer);    }    /**     * <p class="detail">     * 功能: 删除观察者对象     * </p>     *     * @param observer :     * @author Moore     * @date 2019.11.04 10:42:57     */    @Override    public void remove(Observer observer) {        observers.remove(observer);    }    /**     * <p class="detail">     * 功能: 通知观察者     * </p>     *     * @author Moore     * @date 2019.11.04 10:42:58     */    @Override    public void notifyObserver() {        observers.forEach(observer -> observer.update(this));    }    /**     * 发布最新动态     *     * @param basketball     * @param football     * @param showbiz     */    public void publishNewestNews(String basketball, String football, String showbiz) {        this.basketball = basketball;        this.football = football;        this.showbiz = showbiz;        System.out.println("发布最新篮球动态:" + basketball);        System.out.println("发布最新足球动态:" + football);        System.out.println("发布最新娱乐圈动态:" + showbiz + "\n");        this.notifyObserver();    }}

3、编写抽象观察者

package com.mazhichu.designpatterns.observer;/** * <p class="detail"> * 功能: 抽象观察者 * </p> * * @author Moore * @ClassName Observer. * @Version V1.0. * @date 2019.11.04 14:01:06 */public interface Observer {    /**     * 观察者收到通知后更新自己     *     * @param subject 抽象主题对象     * @author Moore     * @date 2019.11.04 14:01:06     */    void update(Subject subject);}

4、编写具体观察者角色类

package com.mazhichu.designpatterns.observer;/** * <p class="detail"> * 功能: 具体观察者角色类 * </p> * * @author Moore * @ClassName Concrete observer. * @Version V1.0. * @date 2019.11.04 14:02:02 */public class ConcreteObserver implements Observer {    /**     * 观察者名称     */    private String name;    // 关注主题分类    private String subjectType;    /**     * 主题对象     */    private Subject subject;    public ConcreteObserver(String name, String subjectType) {        this.name = name;        this.subjectType = subjectType;    }    /**     * 观察者响应(将主题对象传给观察者,观察者自己决定获取哪些信息)     *     * @param subject 抽象主题对象     */    @Override    public void update(Subject subject) {        this.subject = subject;        if (subjectType.equals("篮球")) {            String basketball = ((ConcreteSubject) subject).getBasketball();            System.out.println(name + "关注的虎扑板块是:" + subjectType);            System.out.println(name + "收到" + subjectType + "新闻:" + basketball + "\n");        }        if (subjectType.equals("足球")) {            String football = ((ConcreteSubject) subject).getFootball();            System.out.println(name + "关注的虎扑板块是:" + subjectType);            System.out.println(name + "收到" + subjectType + "新闻:" + football + "\n");        }        if (subjectType.equals("娱乐圈")) {            String showbiz = ((ConcreteSubject) subject).getShowbiz();            System.out.println(name + "关注的虎扑板块是:" + subjectType);            System.out.println(name + "收到" + subjectType + "新闻:" + showbiz + "\n");        }    }}

5、测试类,看看观察者模式的拉模型

package com.mazhichu.designpatterns.observer;public class Test {    public static void main(String[] args) {        ConcreteSubject subject = new ConcreteSubject();        ConcreteObserver zhangsan = new ConcreteObserver("张三","篮球");        ConcreteObserver lisi = new ConcreteObserver("李四","足球");        ConcreteObserver wangwu = new ConcreteObserver("王五","娱乐圈");        subject.add(zhangsan);        subject.add(lisi);        subject.add(wangwu);        String basketball = "湖人103:96战胜马刺";        String football = "梅西成为6冠王";        String showbiz = "《少年的你》票房破10亿";        subject.publishNewestNews(basketball,football,showbiz);        System.out.println("------------------------------------------------\n");        subject.remove(wangwu);        subject.publishNewestNews("骑士不敌独行侠","巴萨遭遇黑色七分钟","胡歌新电影即将上映");    }}

6、查看结果

通过运行结果,我们可以看出拉模型实现了观察者按需获取主题的状态和数据,其实拉模型和推模型最大的区别就是拉模型中观察者持有主题对象,可以直接操作主体对象。

Java内置的观察者模式框架Observable类和Observer接口

这个只是想告诉大家有这个东西,但是并不想在这篇文章里做出详解,网上关于这个有太多的讲解与实例,如果小伙伴们感兴趣,可以自己看一下源码或者看看别人的文章,侧重看一下setChanged()方法。

总结

优点

  1. 观察者模式是松耦合的,改变主题或观察者中的一方,另一方不会受到影像。

  2. 观察者模式支持广播通信,被观察者会向所有注册过的观察者对象发出通知。

  3. 满足“开闭原则”,新增主题(被观察者目标)和观察者都很方便,也不会影响原有角色。

缺点

  1. 观察者过多,通知会花费很多时间。

  2. 如果主题目标和观察者之间存在循环依赖,可能会循环调用造成系统崩溃。

  3. JDK内置的观察者模式,限制了复用能力。

我不能保证我写的文章都是正确的,但是我能保证都是我自己花时间用心写的,所有的代码示例都是原创,所有的理解都只是我个人理解,不能代表官方权威,所以请各位读者阅读时带着批判的眼光,有选择性的认同,谢谢!

文章同步公众号:码之初,每天推送Java技术文章,期待您的关注!

原创不易,转载请注明出处,谢谢!

发布了23 篇原创文章 · 获赞 12 · 访问量 5770

猜你喜欢

转载自blog.csdn.net/u013469325/article/details/105241091