【设计模式】汉堡中的设计模式——观察者模式
目录情景带入
对于爱吃麦当劳的我来说,自然是不想错过各种动态,可是我又不可能【无时无刻】的蹲在店里等新品吧(还是要搬砖的)
那么有没有一种好的方法,在麦当劳【推出新品或发布动态】的时候,我能及时收到通知?
或许有人会说,你把麦当劳内部员工要一下微信不久可以了,这样一有新品,就让他来通知一下你就好啦!
emm理想确实是很丰满的,但是现实却是老子哪里会搭讪....
那难道就没有办法了吗?
怎么可能,你关注下公众号不久可以了???
为什么关注公众号就可以
我们都知道,关注指定公众号之后,只要该公众号发布动态的时候,你就可以在【订阅号消息】中看到新动态,正如下图所示
公众号是基于发布-订阅模式,了解这个跟我们今天要讲的观察者模式有什么关联吗?
发布者-订阅者模式与观察者模式
观察者模式(Observer)
虽然观察者模式也被称为“发布-订阅”模式,但是我认为这个发布-订阅跟接下来要讲的发布者-订阅者模式是不同的
观察者模式的定义是:当对象存在一对多关系的时候,一个对象修改了自身,则会自动通知依赖他的对象
带入我们的例子中,就是有很多个吃货(Observer)“观察着” 麦当劳公众号(Subject)
Observer、Subject是观察者模式的抽象描述,而他的类图类似是如此的,其中Observer是个抽象类或者接口,底下就是真正的观察者,Subject在这里没有设计成抽象的,你也可以让Subject抽象起来,再给出具体的实现类
现在对照着类图,再看一下观察者模式的定义,多个观察者把自己注册到Subject中,当Subject发生变动,便会触发通知方法,而通知方法就会调用各个具体观察者里面的具体实现
这便是观察者模式基本的概念,那么观察者模式的好处究竟是什么?
- 我们可以知道,Subject(被观察者)和Observer(观察者)是抽象耦合的,也即你观察者怎么变化都不会影响到Subject,如果要新增观察者,也只需要新建一个类,然后注册到Subject里面即可
- 拥有触发机制,一个对象更改,关联对象就可以收到通知,甚是方便
那么观察者的缺点又是什么?
- 细心观察定义,Subject里面可能会有多个观察者,那么这个多个,究竟可能会有多少个?不确定,如果有3亿的人关注了麦当劳公众号,那么发布动态的时候,就会通知到3亿人,会耗费很多时间
- 这种真的就是“被通知”,也即观察者不知道中间经历了什么,而只是知道被观察的对象发生了变化(类似一个女生突然找你吃饭,你还觉得很高兴,殊不知也许她刚刚被放飞机了,所以才找你 ,你不知道个中缘由,当然这也未必是坏事:),因为这样你就可以确定,我不需要知道你怎么做,而只需要这么做,我便能被通知到,观看的角度不一样,就会有不同的想法 )
发布-订阅模式(Publisher-Subscriber)
其实发布-订阅模式跟观察者模式之间最大的区别就是Publisher和Subscriber间,还存在着一个中间件来负责调度,发布者不需要知道订阅者是谁,反过来也是一样的
这么一说就有点像Kafka里面的producer-topic-consumer了,简单画个图
当然实际上,消费者从属于消费者群组,一个群组里面的消费者订阅的是同一个主题,每个消费者接收主题一部分分区的消息,也即对应里面具体的partition,这里就不额外展开了
正如上图,producer和consumer是不认识的,但是他们都认识一个家伙,那就是topic,producer充当publisher的角色,把信息发送到topic,消费者监听(订阅)topic,一旦这个topic接收到信息,就会把信息推送给监听的消费者
观察者模式主要是以同步的方式触发机制,而发布-订阅更多的是用在异步的情况下,借助类似Kafka的消息中间件完成组件间的松散耦合
所以其他两者并不等价,只能说有那么几分相似
观察者模式的落地实现
-
首先按照想法,我们得有一个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(); } } }
-
然后还得有观察者对象,观察者可能会有多个,所以我们直接建一个顶层抽象类,然后给出一个【吃货对象】
/** * @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)); } }
-
当然,最好需要有一个客户端给展示起来
/** * @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】,这里将不定期的更新日常工作总结和学习中的重难点知识分享,赶快来订阅我吧!