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工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...
 
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
 
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
 
论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
 
代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
 
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
SpringAI实战:ChatModel智能对话全解
一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM࿰…...
 
sshd代码修改banner
sshd服务连接之后会收到字符串: SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢? 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头,…...
电脑桌面太单调,用Python写一个桌面小宠物应用。
下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡,可以响应鼠标点击,并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...
