java设计模式之观察者模式
. 基本概念
观察者(Observer)模式中包含两种对象,分别是目标对象和观察者对象。在目标对象和观察者对象间存在着一种一对多的对应关系,当这个目标对象的状态发生变化时,所有依赖于它的观察者对象都会得到通知并执行它们各自特有的行为。
通俗地说,就好像这些观察者对象在时刻注视着目标对象(被观察)。无论何时该目标对象的状态发生变化,这些观察者对象都能够马上知道,并根据目标对象的新状态执行相应的任务。
观察者模式又叫发布-订阅(Publish-Subscribe)模式,其中的订阅表示这些观察者对象需要向目标对象进行注册,这样目标对象才知道有哪些对象在观察它。发布指的是当目标对象的状态改变时,它就向它所有的观察者对象发布状态更改的消息,以让这些观察者对象知晓。
一个目标对象的观察者对象数量是不固定的,可以随时增加新的观察者对象或取消已有的观察者对象。观察者模式的主要优点就是极大地降低了目标对象和观察者对象间的耦合,二者可以独自地改变和复用,让对系统增加功能或删除功能都很方便。
2. 应用举例
我们举一个实际的例子来说明对观察者模式的运用,假设我们有一个天气App,它有很多个界面组件,这些组件的作用分别是:显示摄氏温度、显示华氏温度、显示气温感受(比如炎热、凉爽和寒冷等等)。当然该App应该还有另一个对象用于获取实时的天气数据,我们称它为天气对象。
这个天气对象和界面组件之间的依赖关系就可以用观察者模式实现,该天气对象就是目标对象,天气数据就是它的状态。这些界面组件就是观察者对象,当天气对象获取到新的天气数据时(此时它的状态改变了),它就通知所有依赖于它的界面组件,这些组件就更新它们显示的内容。

图2 天气App中的观察者模式
现在我们想扩展这个天气App的功能,让它可以向用户建议如何穿衣。因此我们需要一个新的界面组件,当温度高时显示穿薄点,当温度低时显示穿羽绒服。我们让这个新的界面组件注册成为天气对象(目标对象)的观察者,这样当天气数据改变时,它就会自动得到通知并显示新的穿衣建议。而天气对象和其余的界面组件都不会受到影响,也无需改变。
现在让我们再次修改这个天气App,这次是要删除显示华氏温度的功能。因为我们中国人更习惯摄氏温度,显示华氏温度不但多此一举,还可能让用户误将它当作摄氏温度。此时,我们只需向天气对象(目标对象)取消注册该界面组件再删除它就可以了,天气对象在数据更新时就不会再通知该界面组件了。同样的,该App的其余部分也不会受到本次修改的影响。
3. 结构
我们先直接给出观察者模式的类结构图以及这些类充当的角色和作用,再解释为什么它们是这样的结构。

图3 观察者模式的类结构图
Subject:目标类,它是一个抽象类,也是所有目标对象的父类。它用一个列表记录当前目标对象有哪些观察者对象,并提供增加、删除观察者对象和通知观察者对象的接口。
Observer:观察者类,它也是一个抽象类,是所有观察者对象的父类;它为所有的观察者对象都定义了一个名为update的方法(也叫成员函数)。当目标对象的状态改变时,它就是通过调用它的所有观察者对象的update方法来通知它们的。
ConcreteSubject:具体目标类,可以有多个不同的具体目标类,它们同时继承Subject类。一个目标对象就是某个具体目标类的对象,一个具体目标类负责定义它自身的事务逻辑,并在状态改变时通知它的所有观察者对象。
ConcreteObserver:具体观察者类,可以有多个不同的具体观察者类,它们同时继承Observer类。一个观察者对象就是某个具体观察者类的对象。每个具体观察者类都要重定义Observer类中定义的update方法,在该方法中实现它自己的任务逻辑,当它被通知的时候(目标对象调用它的update方法)就执行自己特有的任务。
注意在Java语言中,Observer类可以用接口(interface)代替,此时所有的具体观察者类都要实现该接口。
3.1 Observer和ConcreteObserver
首先,目标对象需要知道有哪些观察者对象在观察它,这样它才知道状态改变时应该通知哪些观察者对象,并且它还要能随时添加和删除观察者对象。所以目标对象应该要有一个列表,来保存对它的所有观察者对象的引用。但在C++或Java这样的编程语言中,一个列表中的所有项都必须是同一类型的。这说明所有的观察者对象都必须是同一个类,但这样会限制程序的灵活性。因为不同的观察者对象要执行不同的任务,我们应该让它们属于不同的类。解决这一矛盾的途径是让所有的观察者类都有一个共同的父类(Observer),这些观察者类(ConcreteObserver)都是该父类的不同子类。因为所有子类的对象都可以被当作父类的对象,因此它们即可以保持不同又可以保存在同一列表中。
同时,这也解决了另一个问题,那就是目标对象是如何通知它的观察者对象的呢?当然这是通过调用它的观察者对象的一个方法(也叫成员函数)来实现的。目标对象只需要负责通知它的观察者对象它的状态改变了,而对该观察者对象如何处理新状态以及属于哪个具体观察者类都不需要了解。这就是说目标对象只能以同一种方式对待它的所有观察者对象,即这些观察者对象都要有一个相同的方法供目标对象调用,我们称该方法为update方法。
我们在抽象观察者类(Observer)中定义该方法,并在所有具体观察者类(ConcreteObserver)中重定义它。当目标对象的状态改变时,它在它的列表中每遍历到一个观察者对象,就调用该观察者对象的update方法。对目标对象来说,所有的观察者对象都是Observer类的,并且该类确实有一个update方法,所以能调用成功。又因为多态,实际执行的却是该观察者对象所属的具体观察者类中重定义的那个update方法。
要在观察者类中建立这样的继承关系的另一大原因是为了能方便地扩展功能。因为目标对象将所有的观察者对象都当作Observer类的对象来处理,所以要增加某种新的观察者对象时,我们只需创建一个新的类,让它继承Observer类并重定义update方法,在该update方法中实现它自己的任务逻辑就行了。这样所有由该新类创建的观察者对象都可以很容易地融入到当前的观察者模式中,程序的其余部分都不需要改变。
3.2 Subject和ConcreteSubject
那么为什么目标类(目标对象所属的类)也需要有一个抽象目标类(Subject)和多个具体目标类(ConcreteSubject)这样的继承结构呢?如果整个系统中只有一个目标对象,那么确实可以只用一个目标类实现观察者模式。我们之前说的都是多个观察者对象观察一个目标对象,但其实一个观察者对象也可以同时观察多个目标对象。
既然要同时观察多个目标对象,那么它们很可能有不同的状态以及不同的功能,即这些目标对象可能属于不同的类。也就是说一个观察者对象要被多个不同类的目标对象通知到,注意目标对象通知观察者对象是通过调用观察者对象的一个方法实现的。我们先考虑一个笨办法,在观察者类中为每一个目标类都提供一个版本的update方法。这样不仅麻烦而且代码难以维护,想象一下我们要增加一个新的目标类,那么就需要在所有要观察它的观察者类中都增加一个对应版本的update方法;当我们想删掉一个目标类的时候,又要在这些观察者类中删除那个对应版本的update方法。
一个通用的解决方式是让所有的目标类都调用同一个update方法,但是将自身的引用作为该方法的一个参数传递给观察者对象,这样观察者对象就知道是哪一个目标对象在通知它。一个方法(或成员函数)的参数的类型是确定的,也就是说所有的目标类都应该是同一个类。
这就又出现了观察者类中开始的矛盾局面,即所有的目标类既要是同一个类也要是不同的类。解决的方式也是一样的,即所有的目标类都有同一个父类(抽象目标类Subject),而不同的目标类(具体目标类ConcreteSubject)都是该父类的不同子类。
无论一个观察者对象可以同时观察多个目标对象还是只能观察一个目标对象,目标类的这种继承关系都有助于增加新的目标类(即扩展程序的功能)。当我们想增加新的具体目标类时,就创建一个新类,再让它继承Subject类并实现它自己特有的事务逻辑就可以了。这样由该新类所创建的目标对象就可以很轻松地融入到当前的观察者模式中,程序的其余部分都不会受到影响。
抽象目标类(Subject)和具体目标类(ConcreteSubject)以及抽象观察者类(Observer)和具体观察者类(ConcreteObserver)之间的继承关系降低了目标对象(它属于某个具体目标类)和观察者对象(它属于某个具体观察者类)之间的耦合度。
3.3 获取新状态
总的来说,有两种方式可以让观察者对象获取到目标对象的新状态。一是当目标对象调用每个观察者对象的update方法时,将代表它新状态的数据作为该update方法的一个参数传递给该观察者对象。二是目标对象在调用update方法时并不传递新状态,而是该观察者对象在被通知到的时候(在update方法中)再主动去询问该目标对象的新状态是什么。
如果采用第二种方式,那么观察者对象需要拥有它的目标对象的引用,再通过该引用调用目标对象的某个方法,该方法返回目标对象的新状态。该引用可能是目标对象调用观察者对象的update方法时传递来的,也可能是最初将该观察者对象添加进目标对象的列表中(订阅)的时候,设置该观察者对象的某个成员变量让它一直保存对该目标对象的引用。
此时我们可能又要面对既要相同又要不同的问题。先考虑目标对象在调用观察者对象的update方法时传递回它的新状态的方式,我们已知update方法的原型是唯一的(即它的参数数量和类型以及返回值类型是确定了的)。既然所有具体目标类的状态都要作为同一个参数传递,那么这些状态都必须是同一种类型的。但是这些不同的具体目标类的状态很可能不一样,至少在某些细节上有差异,这样看的话它们的状态又很可能是不同类型的。
再考虑上面的第二种方式,即观察者对象在获得通知后再调用目标对象的某个方法,该方法返回目标对象的新状态。我们纠结的是如何在所有的目标类中实现这个状态获取方法,我们称它为getState方法。
3.3.1 用继承方式实现状态获取方法
首先我们可以在目标类的继承关系中实现getState方法,也就是先在抽象目标类(Subject)中定义一个getState方法,然后再在每个具体目标类中重定义该getState方法,使之返回该具体目标类的实际状态。这样做是很容易想到的,因为所有的具体目标类都要有一个功能相似的getState方法,而所有目标类本身就有一个建立好的继承关系。对于所有子类中相似的部分我们应该将它提取到父类中,让子类继承以实现一致性。
另一个原因是,当一个观察者对象同时观察多个目标对象时,这些目标对象都是作为Subject类的对象通过update方法传递给它的。观察者对象将通知它的目标对象都当作Subject类型的对象对待的,可能根本就不知道该目标对象到底属于哪个具体目标类。此时,也要求在Subject父类中为所有类型的目标对象都定义一个相同的状态获取方法。
此时getState方法的实现和update方法是相似的,都是利用了多态的特性让调用父类对象的方法时实际执行的是该对象真正所属的子类中重定义的同名方法。这种实现getState方法的措施限制了getState方法的原型也是唯一的,即它的参数数量和类型以及返回值类型都是确定了的,也就要求所有具体目标类的状态是同一种类型的。
如果所有具体目标类的状态确实是同一种类型或者可以提炼到同一种类中,那么以上两种获取目标对象新状态的方式都是可行的。同时为了保持各个具体目标类的状态的差异化,状态的实现也可以采用继承的方式。定义一个抽象状态类和多个具体状态类,所有具体状态类继承该抽象状态类。每个具体目标类将它对应的具体状态类的对象(代表该具体目标类的状态)作为抽象状态类的对象通过update方法传递给观察者对象或者通过getState方法返回给它们。
3.3.2 每个具体目标类实现不同的状态获取方法
然而现实中也有很多这样的情况:那就是多个具体目标类的状态之间差异太大,根本无法统一为同一种类型。此时只能使用第二种获取目标对象新状态的方式,因为这些不同具体目标类的状态根本不能作为update方法的同一个参数进行传递。
因此各个具体目标类的状态获取方法也会是不一样的,至少它们的返回值类型是不一样的,很可能连方法名也不相同。这样就无法在抽象目标类(Subject)中为所有的具体目标类定义相同的getState方法。
当观察者对象被它的目标对象通知状态改变时,它必须要知道该目标对象是什么具体目标类的,这样它才能调用该具体目标类专有的状态获取方法来获取新状态。但是前文已多次说过所有目标对象都是作为Subject类型传递给观察者对象的,这似乎又是矛盾的。
其实这个问题也可以解决,虽然确实是任意一种具体目标类的目标对象都可以调用任意一个观察者对象的update方法,但实际上一个观察者对象只对某些具体目标类的目标对象感兴趣。因为它需要特定类型的输入数据,而不是任何数据它都可以处理。比如第2小节中,我们那个天气App的例子中的所有观察者对象(界面组件)都只能处理天气数据,而对金融或交通数据都不适用。
通常在设计一个具体观察者类的时候,就已知它所期待的目标对象是属于哪一些具体目标类的,我们也应该只让该类的观察者对象订阅它所期待的那些类的目标对象。当观察者对象的update方法被调用时,它可以检测传递进来的目标对象是否属于它所期待的那些具体目标类,比如Java中的instanceof运算符就可以检测一个对象是否是某种类的对象。如果不是,那么它就忽略本次通知而什么也不做;如果是,它就知道了该目标对象的具体类型,也就能调用该目标对象专有的状态获取方法了。
这样做看似会降低该具体观察者类的复用性,但实际情况是每一个具体观察者类都不是要在任意场景中都可以使用,它本身就是只针对某一领域设计的。上面的观察者模式类图中以及下面我们实现观察者模式的时候,就是用这种方式获取目标对象的新状态的。
4. 实现
现在,让我们来用Java语言和观察者模式实现第2小节中的那个天气App的例子;当然我们不会真的去开发一个功能完整且界面美观的App,我们让这些界面组件打印出它们应该显示的内容来模拟它们的实际功能。

首先,在Observer.java文件中我们定义抽象目标类Subject和接口MyObserver,接口MyObserver充当观察者模式类图结构中的抽象观察者类Observer,我们之前就说过这个抽象父类可以用接口实现。
另一个要注意的是,我们将该接口命名为MyObserver而不是Observer,这是因为Java中本来就有一个自带的名为Observer的接口,它也是用来实现观察者模式的。这里我们想完全实现我们自己的观察者模式,而不使用Java自带的Observer接口。
MyObserver接口只有一个方法update,目标对象通过调用它来通知观察者对象它的状态改变了,该update方法有一个类型为Subject的参数,这允许目标对象将它自己传递给观察者对象,这样一个观察者对象就可以同时观察多个目标对象。

在这个简单例子中只有一个目标对象,因此也只有一个具体目标类,它就是Weather类。为了例子保持简单,这里的天气数据只包含温度;Weather类的状态就是当前的温度值,它通过随机生成一个-80至60的浮点数作为新的温度值来模拟对天气数据的获取,当然实际中的App应该通过一个接口到服务器上获取真实的气象数据。
目标对象必须确保状态确实改变了才通知观察者对象,因此Weather类必须测试新的温度值是否和之前的温度值相等。Weather类的状态获取方法getTemperature是它特有的,没有按照继承关系的方式定义它,即其它具体目标类的状态获取方法是不同的。
在这个例子中,目标对象的状态改变时由它自己调用它的notifyObservers方法通知所有的观察者对象(notifyObservers方法会依次调用每个观察者对象的update方法)。其实也可以在目标对象的状态改变后,由客户代码调用目标对象的notifyObservers方法通知观察者。

在ui.java文件中我们定义两个界面组件类,CelsiusView和WearView,它们都是具体观察者类,因此它们都要实现MyObserver接口。CelsiusView按照摄氏度显示温度值,而WearView则显示穿衣建议。当然这个例子中,它们并不会绘制实际的界面,而是通过一个打印语句来模拟对界面的显示。
虽然我们说过一个观察者对象可以同时观察多个属于不同类的目标对象,但在该例子中的观察者对象只会观察一个目标对象,因为这两个界面组件是用于显示天气信息的,因此对其它类型的目标对象也不感兴趣。所以,当它们被目标对象通知的时候,它们会在检查目标对象确实是一个Weather类的对象后才执行各自的任务。
当这两个观察者对象确实收到来自Weather类的目标对象的通知时,它们就将该目标对象强制转换为Weather类的对象,并通过调用Weather类的专有状态获取方法getTemperature()获取目标对象的新状态。

在App.java文件中,我们定义App类,它模拟该天气App的运行。以上示例代码的执行结果如下图所示,我们可以看到当目标对象的状态改变时(在本例子中是温度值的改变),两个观察者对象都被通知到了并更新了它们的界面显示。

图4 示例代码的执行结果
5. 结语
在某些平台上,可以向目标对象同时注册一个观察者对象和它的某个方法(成员函数)。这样当目标对象的状态改变时,它就调用这个观察者对象的这个注册的方法,而不一定要去调用它的update方法。这就允许这些观察者对象没有update方法,即它们不用都继承自同一个父类。当然一个目标对象可能要求它的所有观察者注册的回调方法都具有相同的函数原型,即这些方法要有相同数量和类型的参数以及相同类型的返回值。
观察者模式还有很多其它的实现方式,但这些方式都只是在一些细节上有所不同。只要理解了观察者模式的主要概念,就能够很容易理解这些细节差异。
相关文章:
java设计模式之观察者模式
. 基本概念 观察者(Observer)模式中包含两种对象,分别是目标对象和观察者对象。在目标对象和观察者对象间存在着一种一对多的对应关系,当这个目标对象的状态发生变化时,所有依赖于它的观察者对象都会得到通知并执行它…...
掌动智能分享:性能压力测试的重要性与优势
在当今数字化时代,应用程序的性能对于用户体验和业务成功至关重要。为了保证应用程序的高性能和稳定性,性能压力测试成为了不可或缺的环节。在这个领域,掌动智能作为一家专业的性能压力测试公司,正以其卓越的技术与服务࿰…...
C# ppt文件转换为pdf文件
使用第三方插件 Office 实现转换 1.Application方式转换 /// <summary>/// Microsoft.Office.Interop.PowerPoint/// 使用第三方软件 office/// </summary>/// <param name"pptPath">需要转换的ppt文件路径</param>/// <param name"…...
使用Pyarmor保护Python脚本不被反向工程
Python可读性强,使用广泛。虽然这种可读性有利于协作,但也增加了未授权访问和滥用的风险。如果未采取适当的保护,竞争对手或恶意攻击者可以复制您的算法和专有逻辑,这将对您软件的完整性和用户的信任产生负面影响。 实施可靠的安…...
STM32单片机——串口通信(轮询+中断)
STM32单片机——串口通信(轮询中断) 串口通信相关概念HAL库解析及CubeMX工程配置与程序设计常用函数介绍CubeMX工程配置HAL库程序设计(轮询中断)轮询数据收发中断收发数据 固件库程序设计及实现固件库配置流程结构体配置及初始化程…...
Python if语句的嵌套应用
视频版教程 Python3零基础7天入门实战视频教程 有时候业务上有多维度复杂条件判断,我们需要用到if语句的嵌套来实现。 举例:我们在一些游戏网站活动充值的时候,冲100送 20 冲200送50 但是vip用户的话,冲100送 30 冲200送70 代码…...
C++中带默认值的函数参数
C中带默认值的函数参数 如果一直将 Pi 声明为常量,没有给用户提供修改它的机会。然而,用户可能希望其精度更高或更低。如何编写一个函数,在用户没有提供的情况下,将 Pi 设置为默认值呢? 为解决这种问题,一…...
记录一次部署Hugo主题lotusdocs到Github Pages实践
引言 随着开源项目的越来越复杂,项目文档的重要性日渐突出。一个好的项目要有一个清晰明了的文档来帮助大家使用。最近一直有在找寻一个简洁明了的文档主题来放置项目的各种相关文档。最终找到这次的主角:Lotus Docs 基于Hugo的主题。Lotus Docs的样子&…...
stm32---基本定时器(TIM6,TIM7)
STM32F1的定时器非常多,由两个基本定时器(TIM6,TIM7)、4个通用定时器(TIM2-TIM5)和两个高级定时器(TIM1,TIM8)组成。基本定时器的功能最为简单&am…...
HTML网页设计
HTML网页设计 HTML网页设计1、常用的单标签2、常用的双标签3、列表标签4、超链接标签5、图像和动画标签6、Html5中音频的插入7、定时刷新或跳转8、表格9、HTML表单标签与表单设计 HTML网页设计 属性值一般用" "括起来,且必须使用英文双引号 <head>…...
阶段性总结:跨时钟域同步处理
对时序图与Verilog语言之间的转化的认识: 首先明确工程要实现一个什么功能;用到的硬件实现一个什么功能。 要很明确这个硬件的工作时序,即:用什么样的信号,什么变化规则的信号去驱动这个硬件。 然后对工程进行模块划…...
[交互]接口与路由问题
[交互]接口与路由问题 场景描述问题分析解决方案 这是在实战开发过程中遇到的一个问题,所以导致产生了服务端如何区分浏览器请求的是前端路由还是 api 接口的问题?? 场景描述 这是一个前后端分离开发的项目,因此前端一般都会使用…...
linux 6中4T磁盘识别并分区格式化挂接
存储端划分4T的LUN后,主机端操作如下 1、主机识别,本例中hba卡的端口是host11和host12 [rootdb1 ~]# echo "- - -" > /sys/class/scsi_host/host11/scan [rootdb1 ~]# echo "- - -" > /sys/class/scsi_host/host12/scan …...
【Unity】ShaderGraph应用(浮动气泡)
【Unity】ShaderGraph应用(浮动气泡) 实现效果 一、实现的方法 1.使用节点介绍 Position:获取模型的顶点坐标 Simple Noise:简单的噪声,用于计算顶点抖动 Fresnel Effect:菲涅耳效应,用于实现气泡效果 计算用节点 Add&…...
Android EditText setTranslationY导致输入法覆盖问题
平台 RK3288 Android 8.1 显示: 1920x1080 160 dpi 概述 碰到一个问题: 弹出的输入法会覆盖文本输入框。 原因:输入框使用了setTranslationY() 位置偏移后, 输入法无法正确获取焦点的位置。 分析 先上图: 初始布局 调用etTranslation…...
MySQL 导出和导入数据
文章目录 一,导出数据(一)使用SELECT ... INTO OUTFILE语句导出数据(二)使用mysqldump工具导出数据(三)使用SELECT ... INTO DUMPFILE语句导出数据 二,导入数据(一&#…...
ubuntu22.04 设置网卡开机自启
配置文件路径 在Ubuntu中,网络配置文件通常位于/etc/netplan/目录下,其文件名以.yaml为后缀。Netplan是Ubuntu 17.10及更高版本中默认的网络配置工具,用于配置网络接口、IP地址、网关、DNS服务器等。 我们可以看到配置文件为 01-network-ma…...
持续部署:提高敏捷加速软件交付(内含教程)
在当今快节奏的数字化环境中,企业不断寻求更快地交付软件、增强客户体验并在竞争中保持领先的方法。持续部署(Continuous Deployment, CD)已成为一种改变游戏规则的方法,使企业能够简化软件交付、提高敏捷性并缩短上市时间。持续部…...
Spark_Spark内存模型管理
工作中经常用到Spark内存调参,之前还没对这块记录,这次记录一下。 环境参数 spark 内存模型中会涉及到多个配置,这些配置由一些环境参数及其配置值有关,为防止后面理解混乱,现在这里列举出来,如果忘记了&a…...
C++之operator=与operator==用法区别(二百一十八)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
《Offer来了:Java面试核心知识点精讲》大纲
文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...
C++--string的模拟实现
一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现,其目的是加强对string的底层了解,以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量,…...
