倾听来自观察者模式中两个成员的经典对话。。。

本文通过观察者模式中Subject与Observer的对话,探讨了推模型与拉模型的选择,并介绍了观察者模式在Java编程中的应用,特别是在JavaBeans事件模型中的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

    一.引子:观察者模式中两个成员的对话

      <<Head First Design Pattern>> 第二章中在讲到观察者模式的时候有段很经典的,是观察者模式中两个角色Subject跟Observer的对话,以下称Subject为S,Observer为O(不是完全按里面翻译的):

      S:很高兴,为了改善咱两的关系,终于等到了今天这个机会跟Observer面对面地来交流。

      O:哦,真的啊?我还以为您从来都没有太在意我们这些Observers啊。

      S:呃,我一直都尽我所能做好我自己的工作啊,不是吗?每每在关键的时刻我都会告诉你们究竟是发生什么事了。。。我不认识你们中的每个人这样不意味着我是不在意你们的啊,而且对于你们来说,有一件最重要的事情我是知道的:你们都实现了Observer这样的接口,每当在关键时候,我告诉你们发生什么事了,你们就可以马上地去处理了。

      O:嗯,确实是这样啊。但是那只是我们身上的一小部分啊,还有很多您是不知道的啊。无论怎样,我对您还是相当了解的。。。

       S:哦,是吗?都了解哪些呢,不妨说来听听。

       O:好的,比如说当您那边的情况一发生了什么变化,您始终都是把这些新“情报”第一时间地传达到我们中的每个Observer,这样我们就时刻都很清楚您那边是处于什么样的状态。但是有时这样也使我们感到很苦恼啊。。。

      S:哦,对此我表示抱歉。我必须把我的状态送给我的通知器比如说notifyObservers()然后让它来通知你们,所以你们这些懒惰的Observers就知道发生了什么事了。

     O:OK,等等,首先我们不是懒惰,因为您一旦通知我们事情有什么变化了,我们得在您Subject先生和您的通知器之间处理很多其它的事情啊;其次,为什么不让我们接近您呢,这样我们就可以很方便地拿到我们每个人真正所关心的那方面啊,而您总是把事情的方方面面都推到了我们每个人的面前,有些对我一点作用也没有。

     S:嗯。。。我想那样会更起作用。跟你说这样的情况,你们Observers正在观察XX公司的一个具有一千多个字段的雇员信息,你们想让我在那些雇员们的某些属性发生变化后告诉你们,那你说在这种情况下我让你们进入我的个人世界,然后让你得到想要的,你们知道哪些属性发生变化吗,不可能的!也许是雇员的薪水变化了,也许是他们换了一个老板。这种情况下将可能是一件很危险的事情啊!所以说我不能让你们进来我的个人世界然后随你们探听察看我所拥有的一切。

   O:那您为什么不留个公开的getter方法,从而我们就可以借助这个getter方法得到我们自己想要的那些啊?

   S:是的,我确实可以让你们拉(pull)到我身上的状态。但是如果像你刚才所说的那样来做的话会更加方便吗?如果每次你都必须到进入我的个人世界然后拿走你们所想要的东西,这样的话你可能就得产生多个调用重复的getter方法来得所有你们想要的东西。这就是为什么我更喜欢把东西直接推(push)给你们的原因了。。。然后再个通知器里面你们就可以直接拿到所需的任何东西了。

  O:不要做得这么的积极啊!在我们这些Observers中,各种各样的性格都有,你无法意料到我们需要的每件东西。就让我们进入您的个人世界,然后拿到我们所需要的东西。像这种方式的话,假如我们当中的某些Observers只是需要某些东西这样的话,这样的话我们就不会被迫接受给予的全部东西了。这样做也很方便以后地扩展啊。现在假设出现这种情况:由于各方面的原因,你壮大了自己,必须增加某种状态,如果是使用让我们自己拿(pull)的方式的话,您就不必为修改我们每个Observer的update方法,然后增加那个新来的东西这样的事情而到处奔波,您只需要在自己身上多写一个getter方法来支持这个新进来的状态就行了。

  S:嗯,其实我能看出来无论是“推我”还是“拉你”都各有各的优点啊。我已经注意到了Java已经有了内置的对我们观察者模式的支持了,它可以允许你自己选择是要推我,还是拉你。

  O:哦,真的?我想我要去感受下这种好处了。。。

  S:嗯,好极了。。。我想我也要去看一个好的“拉你”的例子了,从而改变我的心态啊。。。

  O:什么?我没听错吧,难道咱俩已经达到共识了?我想希望总是有的啊。。。

 (完)

二 究竟是要“推我”还是“拉你”?

   听完观察者模式两个成员的经典对话之后,我想大家对观察者模式会有了更深刻的印象。究竟是要“推我”还是“拉你”,从上面的对话我们很容易下这样结论:完全取决于被观察者对象的复杂性,如果被观察对象比较复杂,并且观察者需要有一个提示,那么推模型是合适的。如果被观察的对象比较简单,那么拉模型就很合适。

三 观察者模式在Java编程中的应用(重点学习JavaBeans事件模型)

      学习设计模式的过程中,一直都有这种感受,看JDK的API甚至是源代码,你会发现比已经更有条理性了,更好理解了,可以说要想研究JDK源代码(我想别的源代码也一样)必须很好地理解了设计模式。有两种方式来查看观察者模式的细节,第一种办法是了解定义在java.utilObserverObservable类。第二种方法是研究注册事件监听器的JavaBeans组件模型。在创建JavaBeans事件模型之前,ObserverObservable类就描述了观察者模式的实现。换句话说,自从Java平台1.0版本,这些类就已经存在了。由于这些类在设计上并没有什么技术错误,因此一直沿用下来了。这些类现在还可以用于实现观察者模式,但是用的更多的是JavaBeans事件模型,我也想花多点时间研究下在这方面的应用,这会对我们在GUI编程方面上有更多的帮助。

    较喜欢这样的划分事件处理模型中的对象:事件对象,事件制造者对象和事件接收者对象。其中,某一个对象是事件的制造者,其余对象是事件的接收者;而事件对象本身则装了有关事件的信息。当事件制造者的内部状态发生了变化时,会根据需要创建一个代表其状变化的事件对象,并将它传给所有登记过的事件接收者对象。对于这种机制,我想大家在听了上面的对话后应该可以很自然地想到这就是建立在观察者模式的基础之上的。其实,从网上资料得知Java1.0的事件处理机制是建立在责任链模式的基础上的(呵呵,写这篇文章的时候还没有学到这种设计模式呢~~)在学习这种事件机制之前,我们先来看看MVC中的Model-View,它们作为MVC模式的一部分,就是Subject和Obverser的关系,因而,模型的改变必须要在UI对象中体现出来。Swing使用了JavaBeans的事件模型来实现这种通知机制。根据前面Subject和Obverserr的对话,有两种实现办法,一是仅仅通知事件监听者状态改变了,然后由事件监听者向模型提取必要的状态信息。这种机制对于事件频繁的组件很有效。另外的一种办法是模型向监听者发送包含了已改变的状态信息的通知给UI。这两种方法根据其优劣被分别是现在不同的组件中。比如在JScollBar中使用的是第一种方法,在JTable中使用的是第二种方法。顺便提下JTable,我们在基于C/S架构的应用中如果要从数据库中取出一批数据然后把它大列表中显示,这时候会用到大量的JTable,到时候我们就可以更好地理解这种机制了。

 

   回到事件监听机制,对Model而言,为了能够支持多个View,它并不知道具体的每一个View。它维护一个对其数据感兴趣的Obverser的列表。调用addXXXListener()方法增加一个监听器,调用removeXXXListener()方法删除一个监听器,这两个方法都要需要一个相应类型的监听器类型。所有的AWT组件都是java.awt.Component的子类,它们都从Component类继承了各个addXXXListener()方法。

  举个小例子:

  ActionListener listener = new ActionListener() {

public void actionPerformed(ActionEvent actionEvent) {

   System.out.println("监听器收到通知!");

  }

};

  JButton button = new JButton("单击我!");

  button.addActionListener(listener);

   在这个例子中,button包括了维护一个监听器聚集类,并提供各种管理监听器对象聚集的方法,比如addActionListener等,可以看出上面的listener就是一个监听器对象它实现了ActionListener接口的actionPerformed方法,这时如果用户单击了button,就会激发actionPerformed方法,其实这里的actionPerformed就相当于Observer中的update()方法。

   在这里,我们实现一个监听器接口,将其附加到监听的对象上。监听的对象就是被监听的。它的职责是记住谁在监听,在JavaBeans组件模型中,用于附加和解除Observer对象的接口是添加和删除监听器命名模式。当监听对象的状态改变的时候,它会通知Observer对象。这种设计模式的一个主要目标就是将对象和观察者的耦合度降低。当JButton被选择的时候,并非调用一个叫做ButtonNotification类的特定方法,通知动作被抽象到一个接口中,任何类都可以实现它。JButton并不关心绑定的监听器是什么。事实上,按钮不关心实现类是不是被修改了。它关心的是观察者实现了这个监听器。

   下面这段摘自网上的资料,和大家分享:

    在使用观察者模式的时候,有很多的复杂问题需要注意。首先是可能出现内存泄露。监听的对象维护着一个观察者的引用。在监听对象释放这个引用之前,垃圾收集器都不能删除观察者。一定要清醒的认识到这种可能,在合适的时候删除掉观察者。另外需要注意的是观察者对象维持在一个无序的集合之中。至少当注册监听器的时候是这样。你没有必要知道先被注册的监听器是先被调用还是后被调用。如果你需要有序的调用,例如A必须先被调用然后是B。那么你必须引入一个中间层的对象来强制这种顺序。简单的按照顺序来注册监听器是不能确保调用顺序的。

   四 总结

  观察者模式是设计模式中的应用相当广泛的一种,希望本文能给大家学习观察者模式起到抛砖引玉的作用。

 

 

<template> <div class="scroll-container" ref="container"> <section v-for="(section, index) in sections" :key="index" :class="[{ active: currentIndex === index }, section.bgClass]" > <div class="section section-one" v-if="index === 0"> <div class="title">{{ section.title }}</div> <img src="./img/phone.png" alt="" /> <div class="content">{{ section.content }}</div> </div> <div class="section section-two" v-if="index === 1"> <div class="title">{{ section.title }}</div> <div class="content">{{ section.content }}</div> </div> <div class="section section-three" v-if="index === 2"> <div> <img src="./img/phone3.png" alt="" /> </div> <div> <div class="title">{{ section.title }}</div> <div class="content">{{ section.content }}</div> </div> </div> <div class="section section-four" v-if="index === 3"> <div> <img src="./img/phone4.png" alt="" /> </div> <div> <div class="title">{{ section.title }}</div> <div class="content">{{ section.content }}</div> </div> </div> <div class="section section-five" v-if="index === 4"> <div class="title">{{ section.title }}</div> <img src="./img/phone5.png" alt="" /> <div class="content">{{ section.content }}</div> </div> </section> <!-- 右侧导航圆点 --> <div class="nav-dots"> <div v-for="(dot, index) in sections" :key="index" class="dot" :class="{ active: currentIndex === index }" @click="scrollToSection(index)" ></div> </div> </div> </template> <script setup> import { ref, onMounted, // onUnmounted } from "vue"; // 页面内容数据 const sections = ref([ { title: "土豆星球", content: "现代人的生活压力通常很大,各行各业皆是如此,你的挚爱亲朋不理解你,你的公司领导瞧不上你,也许是你没能遇到一个懂你、理解你的知己,也许是你的脆弱不希望和身边的人去诉说。来T土豆星球,进行一次心灵测试,我们来帮你找到那个懂你,倾听你的他/她,让相似经历的你们,彼此陪伴,伴你走出人生的低谷。", bgClass: "bg1", }, { title: "穿越过去", content: "每个人都曾经历过痛苦的离别,每个人的内心深处都有很多来不及说出的话,时空胶囊能让你和失去/逝去的人隔空对话,任何时间任何地点,点开土豆星球,打开时空胶囊,都能让你回到过去,跟爷爷奶奶说一句:“家里一切都好,你们好吗?” 跟父母说一句:“我爱你们”公司基于强大的技术实力和元宇宙的星现形式,为你补完内心深处的遗憾。", bgClass: "bg2", }, { title: "心灵对话", content: "在遭受困难时,你会不会感叹命运的不公?为什么别人过的那么好,而你却什么也不是?很多人因此失去了人生的目标和对未来的希望。你是否希望未来的自己能穿越回现在给你指引方向?其实每个人都不普通,只是你还没找到自己的闪光点,让我们站在未来的高处,跨域时间和现实的梏,让未来的你,伸手将今天的自己拉出困境低谷。 ", bgClass: "bg3", }, { title: "匹配社交", content: "现代人的生活压力通常很大,各行各业皆是如此,你的挚爱亲朋不理解你,你的公司领导瞧不上你,也许是你没能遇到一个懂你、理解你的知己,也许是你的脆弱不希望和身边的人去诉说。来T土豆星球,进行一次心灵测试,我们来帮你找到那个懂你,倾听你的他/她,让相似经历的你们,彼此陪伴,伴你走出人生的低谷。", bgClass: "bg4", }, { title: "寻找话题", content: "我们每个人都是不同的个体,有不同的价值观,或许事情没有绝对的对错,只是缺少聊得来的人。网络虽然大,但依旧无法分享你喜欢的聊天话题,那些在你心里积攒了无数个日月的话,想找别人分享,但一直没有找到分享的对象,那么土豆星球是一个你能够展示自己的平台,有一群和你志同道合的小伙伴在等着你!", bgClass: "bg5", }, ]); const container = ref(null); const currentIndex = ref(0); let isScrolling = false; // 处理滚动事件 const handleWheel = (e) => { if (isScrolling) return; isScrolling = true; const direction = e.deltaY > 0 ? 1 : -1; const newIndex = Math.min( Math.max(currentIndex.value + direction, 0), sections.value.length - 1 ); if (newIndex !== currentIndex.value) { currentIndex.value = newIndex; container.value.scrollTo({ top: window.innerHeight * newIndex, behavior: "smooth", }); } setTimeout(() => { isScrolling = false; }, 800); }; // 添加键盘支持 const handleKeyDown = (e) => { if (e.key === "ArrowDown") { currentIndex.value = Math.min( currentIndex.value + 1, sections.value.length - 1 ); } else if (e.key === "ArrowUp") { currentIndex.value = Math.max(currentIndex.value - 1, 0); } container.value.scrollTo({ top: window.innerHeight * currentIndex.value, behavior: "smooth", }); }; // 添加触摸滑动支持 let touchStartY = 0; const handleTouchStart = (e) => { touchStartY = e.touches[0].clientY; }; const handleTouchEnd = (e) => { const touchEndY = e.changedTouches[0].clientY; const diff = touchStartY - touchEndY; if (Math.abs(diff) > 50) { const direction = diff > 0 ? 1 : -1; const newIndex = Math.min( Math.max(currentIndex.value + direction, 0), sections.value.length - 1 ); if (newIndex !== currentIndex.value) { currentIndex.value = newIndex; container.value.scrollTo({ top: window.innerHeight * newIndex, behavior: "smooth", }); } } }; const scrollToSection = (index) => { currentIndex.value = index; container.value.scrollTo({ top: window.innerHeight * index, behavior: "smooth", }); }; // 生命周期钩子 onMounted(() => { window.addEventListener("wheel", handleWheel, { passive: false }); window.addEventListener("keydown", handleKeyDown); container.value.addEventListener("touchstart", handleTouchStart); container.value.addEventListener("touchend", handleTouchEnd); }); // onUnmounted(() => { // window.removeEventListener('wheel', handleWheel) // window.removeEventListener('keydown', handleKeyDown) // container.value.removeEventListener('touchstart', handleTouchStart) // container.value.removeEventListener('touchend', handleTouchEnd) // }) </script> <style scoped lang="scss"> .scroll-container { height: 100vh; overflow-y: hidden; scroll-snap-type: y mandatory; scroll-behavior: smooth; position: relative; } section { height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; scroll-snap-align: start; transition: background-color 0.5s ease; position: relative; background-size: 100% 100%; background-repeat: no-repeat; } .bg1 { background-image: url("./img/bg1.png"); } .bg2 { background-image: url("./img/bg2.png"); } .bg3 { background-image: url("./img/bg3.png"); } .bg4 { background-image: url("./img/bg4.png"); } .bg5 { background-image: url("./img/bg5.png"); } .title { font-family: Alibaba-PuHuiTi, Alibaba-PuHuiTi; font-weight: 500; font-size: 80px; color: #e1e1e1; text-align: left; font-style: normal; text-transform: none; } .content { font-family: Adobe Heiti Std; font-size: 14px; color: #ffffff; line-height: 30px; text-align: justifyLeft; font-style: normal; text-transform: none; } .section-one { width: 100%; display: flex; justify-content: space-around; align-items: center; .content { width: 25%; } } .section-two { width: 30%; position: absolute; right: 150px; bottom: 180px; .content { margin-top: 60px; } } .section-three { width: 100%; display: flex; flex-wrap: nowrap; justify-content: space-around; align-items: center; .content { width: 600px; margin-top: 50px; } } .section-four { width: 100%; display: flex; justify-content: space-around; .title { font-size: 100px; font-weight: 700; margin-top: 100px; } .content { width: 600px; margin-top: 50px; } } .section-five { width: 100%; display: flex; justify-content: space-around; position: relative; .title { position: relative; margin-top: 500px; } .content { position: relative; margin-bottom: 200px; width: 600px; } } .nav-dots { position: fixed; right:40px; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; gap: 16px; z-index: 1000; } .dot { width: 8px; height: 8px; background-color: rgba(255, 255, 255, 0.6); border-radius: 50%; cursor: pointer; transition: background-color 0.3s ease; } .dot.active { background-color: #363636; transform: scale(1.7) } @keyframes bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-20px); } 60% { transform: translateY(-10px); } } /* 最后一个区块不显示指示器 */ section:last-child .scroll-indicator { display: none; } /* 响应式设计 */ @media (max-width: 768px) { h1 { font-size: 2rem; } p { font-size: 1.2rem; } } </style> 实现图片懒加载
最新发布
07-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值