iamamg97

【设计模式】汉堡中的设计模式——观察者模式

情景带入

对于爱吃麦当劳的我来说,自然是不想错过各种动态,可是我又不可能【无时无刻】的蹲在店里等新品吧(还是要搬砖的)

那么有没有一种好的方法,在麦当劳【推出新品或发布动态】的时候,我能及时收到通知?

或许有人会说,你把麦当劳内部员工要一下微信不久可以了,这样一有新品,就让他来通知一下你就好啦!

emm理想确实是很丰满的,但是现实却是老子哪里会搭讪....

那难道就没有办法了吗?

怎么可能,你关注下公众号不久可以了???

image

为什么关注公众号就可以

我们都知道,关注指定公众号之后,只要该公众号发布动态的时候,你就可以在【订阅号消息】中看到新动态,正如下图所示

image

公众号是基于发布-订阅模式,了解这个跟我们今天要讲的观察者模式有什么关联吗?

发布者-订阅者模式与观察者模式

观察者模式(Observer)

虽然观察者模式也被称为“发布-订阅”模式,但是我认为这个发布-订阅跟接下来要讲的发布者-订阅者模式是不同

观察者模式的定义是:当对象存在一对多关系的时候,一个对象修改了自身,则会自动通知依赖他的对象

带入我们的例子中,就是有很多个吃货(Observer)“观察着” 麦当劳公众号(Subject)

Observer、Subject是观察者模式的抽象描述,而他的类图类似是如此的,其中Observer是个抽象类或者接口,底下就是真正的观察者,Subject在这里没有设计成抽象的,你也可以让Subject抽象起来,再给出具体的实现类

image

现在对照着类图,再看一下观察者模式的定义,多个观察者把自己注册到Subject中,当Subject发生变动,便会触发通知方法,而通知方法就会调用各个具体观察者里面的具体实现

这便是观察者模式基本的概念,那么观察者模式的好处究竟是什么?

  1. 我们可以知道,Subject(被观察者)和Observer(观察者)是抽象耦合的,也即你观察者怎么变化都不会影响到Subject,如果要新增观察者,也只需要新建一个类,然后注册到Subject里面即可
  2. 拥有触发机制,一个对象更改,关联对象就可以收到通知,甚是方便

那么观察者的缺点又是什么?

  1. 细心观察定义,Subject里面可能会有多个观察者,那么这个多个,究竟可能会有多少个?不确定,如果有3亿的人关注了麦当劳公众号,那么发布动态的时候,就会通知到3亿人,会耗费很多时间
  2. 这种真的就是“被通知”,也即观察者不知道中间经历了什么,而只是知道被观察的对象发生了变化(类似一个女生突然找你吃饭,你还觉得很高兴,殊不知也许她刚刚被放飞机了,所以才找你 ,你不知道个中缘由,当然这也未必是坏事:),因为这样你就可以确定,我不需要知道你怎么做,而只需要这么做,我便能被通知到,观看的角度不一样,就会有不同的想法 )

发布-订阅模式(Publisher-Subscriber)

其实发布-订阅模式跟观察者模式之间最大的区别就是Publisher和Subscriber间,还存在着一个中间件来负责调度,发布者不需要知道订阅者是谁,反过来也是一样的

这么一说就有点像Kafka里面的producer-topic-consumer了,简单画个图

image

当然实际上,消费者从属于消费者群组,一个群组里面的消费者订阅的是同一个主题,每个消费者接收主题一部分分区的消息,也即对应里面具体的partition,这里就不额外展开了

正如上图,producer和consumer是不认识的,但是他们都认识一个家伙,那就是topic,producer充当publisher的角色,把信息发送到topic,消费者监听(订阅)topic,一旦这个topic接收到信息,就会把信息推送给监听的消费者

观察者模式主要是以同步的方式触发机制,而发布-订阅更多的是用在异步的情况下,借助类似Kafka的消息中间件完成组件间的松散耦合

所以其他两者并不等价,只能说有那么几分相似

观察者模式的落地实现

  1. 首先按照想法,我们得有一个McDonalds的Subject,假设这就是【麦当劳官方公众号】,里面有动态发布的方法、注册/移除观察者的方法、以及一键消息提醒的方法

    /**
     * @Author: Amg
     * @Date: Created in 23:04 2021/11/15
     * @Description: 麦当劳公众号
     */
    public class McDonalds {
    
        //观察者的集合,需要这里这里泛型接收的是观察者的顶层类
        private List<Observer> list = new ArrayList<>();
        //接收新动态
        public String status;
    
        public McDonalds(String status) {
            this.status = status;
        }
    
        /**
         * 添加观察者
         * @param observer
         */
        public void registerObserver(Observer observer) {
            list.add(observer);
        }
    
        /**
         * 移除观察者
         * @param observer
         */
        public void removeObserver(Observer observer) {
            list.remove(observer);
        }
    
        /**
         * 遍历集合,取出每一个观察者,然后调用他们的update方法
         */
        public void notifyAllObserver() {
            System.out.println("[麦当劳]:" + status);
            for (Observer observer : list) {
                observer.update();
            }
        }
    
    }
    
    
  2. 然后还得有观察者对象,观察者可能会有多个,所以我们直接建一个顶层抽象类,然后给出一个【吃货对象】

    /**
     * @Author: Amg
     * @Date: Created in 23:04 2021/11/15
     * @Description: TODO
     */
    public abstract class Observer {
        //名字
        protected String name;
        //话语
        protected String say;
    
        public Observer(String name,String say) {
            this.name = name;
            this.say = say;
        }
    
        abstract void update();
    }
    
    
    /**
     * @Author: Amg
     * @Date: Created in 23:10 2021/11/15
     * @Description: 吃货对象
     */
    public class FoodieFan extends Observer {
    
        public FoodieFan(String name, String say) {
            super(name,say);
        }
    
        @Override
        void update() {
            System.out.println(String.format("[%s: %s]",name,say));
        }
    }
    
  3. 当然,最好需要有一个客户端给展示起来

    /**
     * @Author: Amg
     * @Date: Created in 23:08 2021/11/15
     * @Description: 客户端
     */
    public class Client {
    
        public static void main(String[] args) {
    
            McDonalds mcDonalds = new McDonalds("[麦当劳新品推荐:敷面膜的安格斯,当前仅需41.5,赶快来品尝吧!]");
    
            FoodieFan foodieA = new FoodieFan("吃货Amg","这个汉堡可不能错过!");
            FoodieFan foodieB = new FoodieFan("吃货胖头鱼","emm这个我倒是没什么兴趣,有新品雪糕再通知我好了");
            FoodieFan foodieC = new FoodieFan("吃货大头虾","看来今晚的宵夜有着落了,这就去盘他");
    
            mcDonalds.registerObserver(foodieA);
            mcDonalds.registerObserver(foodieB);
            mcDonalds.registerObserver(foodieC);
    
            mcDonalds.notifyAllObserver();
        }
    }
    
    //最终输出的结果
    
    [麦当劳]:[麦当劳新品推荐:敷面膜的安格斯,当前仅需41.5,赶快来品尝吧!]
    [吃货Amg: 这个汉堡可不能错过!]
    [吃货胖头鱼: emm这个我倒是没什么兴趣,有新品雪糕再通知我好了]
    [吃货大头虾: 看来今晚的宵夜有着落了,这就去盘他]
    

只要思想不滑坡,代码还不是手到擒来,所以理解思想才是最关键的

总结

再总结一下观察者面包与发布者-订阅者模式之间的区别

观察者模式 发布-订阅模式
Subject和Observer之间是直接沟通的 Publisher和Subscriber是不直接沟通,通过中间件来交流
观察者模式适用于单体应用程序 发布-订阅模式适用于跨应用的程序
观察者主要以同步方式实现 发布-订阅则主要以异步的方式实现

最后来一波王婆卖瓜,更多精彩尽在微信公众号【码农Amg】,这里将不定期的更新日常工作总结和学习中的重难点知识分享,赶快来订阅我吧!
image

相关文章: