一文清晰讲明白DDD(领域驱动设计)的知识点

news/2024/5/5 23:26:14/文章来源:https://blog.csdn.net/tang_huan_11/article/details/126994232

什么是DDD

DDD(领域驱动设计)是一种处理高度复杂领域的设计思想,是一种架构设计方法论,是一种设计模式。以高内聚低耦合为目的,把一个复杂的软件应用系统中各个部分进行一个很好的拆解和封装,对软件系统进行模块化的一种思想。DDD不仅可以用于微服务设计,还可以很好地应用于企业中台的设计,也适用于传统的单体应用。

在这里插入图片描述

领域模型是什么?

领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和行为,并且表达了准确的业务含义。

领域分为问题空间(problem space)和解决方案空间(solution space)。

在这里插入图片描述

问题空间是领域的一部分,对问题空间的开发将产生一个新的核心域。对问题空间的评估应该同时考虑已有子域和额外所需子域。因此,问题空间是核心域和其他子域的组合。

解决方案空间包括一个或多个限界上下文,即一组特定的软件模型。这是因为限界上下文即是一个特定的解决方案,它通过软件的方式来实现解决方案。

我们为什么需要DDD

第一:使领域专家和开发者在一起工作,这样开发出来的软件能够准确地传达业务规则;

第二:准确传达业务规则;

第三:可以帮助业务人员自我提高,在DDD中,每个人都在学习,同时每个人又是知识的贡献者;

第四:在DDD中,每个人都在学习,同时每个人又是知识的贡献者;

第五:减少沟通成本,当大家都使用相同的语言进行交流时,每人都能听懂他人所说;

第六:设计就是代码,代码就是设计;

第七: DDD同时提供了战略设计和战术设计两种方式。战略设计帮助我们理解哪些投入是最重要的;哪些既有软件资产是可以重新拿来使用的;哪些人应该被加到团队中?战术设计则帮助我们创建DDD模型中各个部件。

DDD 的基础概念

领域与子域

在研究和解决业务问题时,DDD会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。简言之,DDD的领域就是这个边界内要解决的业务问题域。领域是用来确定范围的,范围即边界,在DDD中一直在强调边界,就是这个原因。

领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。

领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小服务需要解决的问题域,构建合适的领域模型。

通用语言

通用语言就是能够简单、清晰、准确描述业务涵义和规则的语言。

通用语言是团队统一的语言,不管你在团队中承担什么角色,在同一个领域的软件生命周期里都使用统一的语言进行交流。那么,通用语言的价值也就很明了,它可以解决交流障碍这个问题,使领域专家和开发人员能够协同合作,从而确保业务需求的正确表达。

这个通用语言到场景落地,大家可能还很模糊,其实就是把领域对象、属性、代码模型对象等,通过代码和文字建立映射关系,可以通过Excel记录这个关系,这样研发可以通过代码知道这个含义,产品或者业务方可以通过文字知道这个含义,沟通起来就不会有歧义,说的简单一点,其实就是统一产品和研发的话术。

界限上下文

限界上下文是用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。
限界上下文是一个显式的边界,领域模型便存在于这个边界之内;在边界内,通用语言中的所有术语和词组都有特定的含义,而模型需要准确地反映通用语言。

限界上下文并不只局限于容纳模型,它通常标定了一个系统、一个应用程序或者一种业务服务。

命名方式:“模型名+上下文”;

上下文映射图

上下文映射图的两种表示方式:

  • 画一个简单的框图来表示两个或多个限界上下文之间的映射关系;该框图表示了不同的限界上下文在解决方案空间中是如何通过集成相互关联的。
  • 更详细的方式是通过限界上下文集成的源代码实现来表示,

实体

实体 = 唯一身份标识 + 可变性【状态 + 行为】

DDD中要求实体是唯一的且可持续变化的。意思是说在实体的生命周期内,无论其如何变化,其仍旧是同一个实体。唯一性由唯一的身份标识来决定的。可变性也正反映了实体本身的状态和行为。

在设计实体时,我们首先需要考虑实体的本质特征,特别是实体的唯一标识和对实体的查找。只有在对实体的本质特征有用的情况下,才加入相应的属性和行为。

实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。我们可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。但是,由于它们拥有相同的 ID,它们依然是同一个实体。比如商品是商品上下文的一个实体,通过唯一的ID来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品。

值对象

值对象 = 将一个值用对象的方式进行表述,来表达一个具体的固定不变的概念。

值对象可以用于存放实体的唯一标识。值对象是不变(immutable)的,这可以保证实体身份的稳定性,并且与身份标识相关的行为也可以得到集中处理。

当你只关心某个对象的属性时,该对象便可作为一个值对象。 我们需要将值对象看成不变对象,不要给它任何身份标识,还应该尽量避免像实体对象一样的复杂性。

聚合

我们把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。聚合是领域对象的显式分组,旨在支持领域模型的行为和不变性,同时充当一致性和事务性边界。

聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的服务很自然就是“高内聚、低耦合”的。

聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。

聚合有以下5个通用的设计原则:
(1)在一致性边界之内建模真正的不变条件
聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。
(2) 设计小聚合
如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。

(3) 通过唯一标识引用其他聚合
聚合之间是通过关联外部聚合根ID的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。

(4) 在边界之外使用最终一致性

聚合内数据强一致性,而聚合之间数据最终一致性。在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间的解耦。

(5)通过应用层实现跨聚合的服务调用
为实现微服务内聚合之间的解耦,以及未来以聚合为单位的微服务组合和拆分,应避免跨聚合的领域服务调用和跨聚合的数据库表关联。

从长远看来,遵循聚合原则对整个项目是有益的。我们将尽可能地保证一致性,并且致力于创建高性能的、高可伸缩性的系统。

聚合实现步骤如下:
(1) 创建具有唯一标识的根实体
将实体建模成聚合根(Aggregat Root);每个聚合根必须拥有一个全局的唯一标识。
(2)优先使用值对象
我们应该尽量地将根实体所包含的其他聚合建模成值对象,而不是实体。在不至于对模型或基础设施造成明显影响的情况下,采用值对象全部替换的方式是最好的选择。
(3)使用迪米特法则和“告诉而非询问”原则
我们需要在迪米特法则和“告诉而非询问”原则之间进行权衡。前者的限制性更强,它只允许客户端通过聚合根进行访问。另一方面,“告诉而非询问”原则则允许客户端访问聚合根的内部,但是它也要求对聚合状态的修改应该属于聚合本身,而不是客户端。因此,在多数情况下,“告诉而非询问”原则将更加适用。
(4)乐观并发
在我们定义聚合时,最安全的方法便是只为根实体创建版本号。每次在聚合内部执行状态修改命令时,根实体的版本号都会随之增加。
(5)避免依赖注入

聚合根

如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。
首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。最后在聚合之间,它还是聚合对外的接口人,以聚合根ID关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,聚合之间通过聚合根ID关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。

聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。

贫血模型

所谓的贫血模型是在定义对象时,指定以对象的属性信息,却没有对象的行为信息,最后再通过添加一些对象属性的get/set方法来赋值取值操作。

这些贫血对象在设计之初就被定义为只能包含数据,不能加入领域逻辑;所有的业务逻辑是放在所谓的业务层,需要使用这些模型来传递数据。

战略设计

战略设计强调的是业务战略上的重点,如何按重要性分配工作,以及如何进行最佳整合。

战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。

战略设计会建立领域模型,领域模型可以用于指导微服务的设计和拆分。事件风暴是建立领域模型的主要方法,它是一个从发散到收敛的过程。它通常采用用例分析、场景分析和用户旅程分析,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系,这是一个发散的过程。事件风暴过程会产生很多的实体、命令、事件等领域对象,我们将这些领域对象从不同的维度进行聚类,形成如聚合、限界上下文等边界,建立领域模型,这就是一个收敛的过程。

战术设计

战术设计犹如使用一把精小的画笔在领域模型上描绘着每个细枝末节。其中一个比较重要的工具被用来将若干实体和值对象以恰当的大小聚集在一起。这就是聚合(Aggregate)模式。

战术设计则从技术视角出发,侧重于领域模型的技术实现完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

DDD就是以最明确而又可行的方式对领域进行建模。使用领域事件(Domain Events)既可以让你明确地建立模型,也可把模型内部发生的事情分享给需要知道这一切的系统

领域服务

领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。当某个操作不适合放在聚合和值对象上时,最好的方式便是使用领域服务了

可以使用领域服务的情况:

  • 执行一个显著的业务操作
  • 对领域对象进行转换
  • 以多个领域对象作为输入参数进行计算,结果产生一个值对象

应用服务

应用层作为展现层与领域层的桥梁,是用来表达用例和用户故事的主要手段。

应用层通过应用服务接口来暴露系统的全部功能。在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。

应用层相对来说是较“薄”的一层,除了定义应用服务之外,在该层我们可以进行安全认证,权限校验,持久化事务控制,或者向其他系统发生基于事件的消息通知,另外还可以用于创建邮件以发送给客户等。

领域事件

将领域中所发生的活动建模成一系列的离散事件。每个事件都用领域对象来表示……领域事件是领域模型的组成部分,表示领域中所发生的事情。

领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。

领域事件是一个领域模型中极其重要的部分,用来表示领域中发生的事件。忽略不相关的领域活动,同时明确领域专家要跟踪或希望被通知的事情,或与其他模型对象中的状态更改相关联,领域事件包括以下几种:
(1)事件发布:构建一个事件,需要唯一标识,然后发布;
(2)事件存储:发布事件前需要存储,因为接收后的事建也会存储,可用于重试或对账等;
(3)事件分发:服务内直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等;
(4)事件处理:先将事件存储,然后再处理。

在做用户旅程或者场景分析时,我们要捕捉业务、需求人员或领域专家口中的关键词:“如果发生……,则……”“当做完……的时候,请通知……”“发生……时,则……”等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。

领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。

领域事件的执行需要一系列的组件和技术来支撑;领域事件处理包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等,如下图所示:
在这里插入图片描述

资源库(仓储)

仓储介于领域模型和数据模型之间,主要用于聚合的持久化和检索。它隔离了领域模型和数据模型,以便我们关注于领域模型而不需要考虑如何进行持久化。

我们将暂时不使用的领域对象从内存中持久化存储到磁盘中。当日后需要再次使用这个领域对象时,根据 key 值到数据库查找到这条记录,然后将其恢复成领域对象,应用程序就可以继续使用它了,这就是领域对象持久化存储的设计思想。

DDD 架构

分层架构

在分层架构中,我们将领域模型和业务逻辑分离出来,并减少对基础设施、用户界面甚至应用层逻辑的依赖,因为它们不属于业务逻辑。将一个复杂的系统分为不同的层,每层都应该具有良好的内聚性,并且只依赖于比其自身更低的层。

典型的DDD系统所采用的传统分层架构,其中核心域只位于架构中的其中一层,其上为用户界面层(User Interface)和应用层(Application Layer),其下是基础设施层(Infrastructure Layer)
DDD经典的分层架构如下图所示:
在这里插入图片描述

分层架构的一个重要原则是:每层只能与位于其下方的层发生耦合。分层架构也分为几种:在严格分层架构(Strict Layers Architecture)中,某层只能与直接位于其下方的层发生耦合;而松散分层架构(Relaxed Layers Architecture)则允许任意上方层与任意下方层发生耦合。

较低层也是可以和较高层发生耦合的,但这只局限于采用观察者(Observer)模式或者调停者(Mediator)模式[Gamma et al.]的情况。

如果用户界面使用了领域模型中的对象,那么此时的领域对象仅限于数据的渲染展现。在采用这种方式时,可以使用展现模型(Presentation Model)对用户界面与领域对象进行解耦。

用户接口层是应用层的直接客户。

应用服务(Application Services)位于应用层中;应用服务可以用于控制持久化事务和安全认证,或者向其他系统发送基于事件的消息通知,另外还可以用于创建邮件以发送给用户。应用服务本身并不处理业务逻辑,但它却是领域模型的直接客户。应用服务是很轻量的,它主要用于协调对领域对象的操作;应用服务是表达用例和用户故事(user story)的主要手段。因此,应用服务的通常用途是:接收来自用户界面的输入参数,再通过资源库获取到聚合实例,然后执行相应的命令操作。当需要创建新的聚合时,应用服务应该使用工厂(Factory,11)或聚合的构造函数来实例化对象,然后采用资源库对其进行持久化。应用服务还可以调用领域服务来完成和领域相关的任务操作,但此时的操作应该是无状态的。当领域模型用于发布领域事件(Domain Events)时,应用层可以将订阅方注册到任意数量的事件上。

SaaSOvation的开发团队发现,将基础设施层放在最底层是存在缺点的。比如,此时领域层中的一些技术实现是令人头疼的,因为他们违背了分层架构的基本原则。针对这个情况,可以根据依赖倒置原则(Dependency Inversion Principle,DIP) 改进分层架构,根据定义,低层服务(比如基础设施层)应该依赖于高层组件(比如用户界面层、应用层和领域层)所提供的接口。改进分层架构图如下:
在这里插入图片描述

采用依赖倒置原则,使领域层和基础设施层都只依赖于由领域模型所定义的抽象接口。由于应用层是领域层的直接客户,它将依赖于领域层接口,并且间接地访问资源库和由基础设施层提供的实现类。应用层可以采用不同的方式来获取这些实现,包括依赖注入(Dependency Injection)、服务工厂(Service Factory)和插件(Plug In)。

六边形架构(端口与适配器)-首选架构

六边形架构也称为端口与适配器。对于每种外界类型,都有一个适配器与之相对应。外界通过应用层API与内部进行交互。六边形架构图如下:
在这里插入图片描述

我们可以将端口想成是HTTP,而将适配器想成是Java的Servlet或JAX-RS的REST请求处理类;在这种情况下,端口是消息机制,而适配器则是消息监听器,因为消息监听器将负责从消息中提取数据,并将数据转化为应用层API(领域模型的客户)所需的参数。

在使用六边形架构时,我们应该根据用例来设计应用程序;任何客户都可能向不同的端口发出请求,但是所有的适配器都将使用相同的API。应用程序边界,即内部六边形,也是用例边界。

六边形架构的好处是我们可以轻易地开发用于测试的适配器。

面向服务架构(SOA)

面向服务设计的有八大原则

服务设计原则描述
服务契约通过契约文档,服务阐述自身的目的与功能
松耦合服务将依赖关系最小化
服务抽象服务只发布契约,而向客户隐藏内部逻辑
服务重用性一种服务可以被其他服务所重用
服务自治性服务自行控制环境与资源以保持独立性,这有助于保持服务的一致性和可靠性
服务无状态性服务负责消费放的状态管理,这不能与服务的自治性发生冲突
服务可发现性客户可以通过服务元数据来查找服务和理解服务
服务组合型一种服务可以由其他的服务组合而成,而不管其他服务的大小和复杂性如何。

将服务的8大原则与六边形架构结合起来,此时服务边界位于最左侧,而领域模型位于中心位置,如下图所示:
在这里插入图片描述

消费方可以通过REST、SOAP和消息机制获取服务。技术服务可以是REST资源、SOAP接口或者消息类型。业务服务强调业务战略,即如何对业务和技术进行整合。

命令和查询职责分离(CQRS )

CQRS是将紧缩(Stringent)对象(或者组件)设计原则和命令-查询分离(CQS)应用在架构模式中的结果。CQRS旨在解决数据显示复杂性问题。

查询模型也被称为读模型,命令模型也被称为写模型。领域模型将被一分为二,命令模型和查询模型分开进行存储。最终,我们得到的组件系统如图:
在这里插入图片描述

在CQRS中,来自客户端的命令通过单独的路径抵达命令模型,而查询操作则采用不同的数据源,这样的好处在于可以优化对查询数据的获取,比如用于展现、用于接口或报告的数据。

###事件驱动架构
事件驱动架构(Event-Driven Architecture,EDA)是一种用于处理事件的生成、发现和处理等任务的软件架构。
在这里插入图片描述

在一个事件驱动架构中融入了六边形架构风格。该事件驱动架构通过消息机制完成对所有系统的解耦。

一个系统的输出端口所发出的领域事件将被发送到另一个系统的输入端口,此后输入端口的事件订阅方将对事件进行处理。对于不同的限界上下文来说,不同的领域事件具有不同含义,也有可能没有任何含义。在一个限界上下文处理某个事件时,应用程序API将采用该事件中的属性值来执行相应的操作。应用程序API所执行的命令操作将反映到命令模型中。

有时,我们的业务可能需要对发生在领域对象上的修改进行跟踪。跟踪每个源文件的修改应用在单个实体上,然后用在单个聚合上,再用于模型中的每个聚合,那么我们便能体会到在对象层面上跟踪变化的好处,进而体会到变化跟踪对于整个系统的好处。而事件源的核心便是变化跟踪,事件源是对于某个聚合上的每次命令操作,都有至少一个领域事件发布出去,该领域事件描述了操作的执行结果。事件源模式如下:
在这里插入图片描述

从高层次看事件源,由聚合发布的事件被保存到事件存储中,同时这些事件被用于跟踪模型的状态变化。资源库从事件存储中读取事件,并将这些事件应用于对聚合状态的重建。

事件源有助于获得高吞吐量的领域模型,从而极大地提高事务处理效率。事件源还有助于提高CQRS查询模型的伸缩性,因为此时查询模型的数据源可以在事件存储更新之后得到静默更新。

数据网织和基于网格的分布式计算

数据网织(Data Fabric),有时也称为网格计算,可以解决大数据计算。数据网织的一个好处是它对领域模型提供了自然的支持,几乎消除了所有的阻抗失配。事实上,分布式缓存可以非常容易地对领域模型进行持久化,此时可以将它看成是一种聚合存储(Aggregate Store)。简单地说,在数据网织中,聚合即是基于图的缓存中的值部分,而聚合的唯一标识则是标识键。这里的键即是聚合的唯一标识。聚合的状态将被持久化为二进制数据或文本数据。

数据网织可以很好地支持事件驱动架构风格,因为它能确保对事件的投递。大多数数据网织都有内建的事件支持,即可以对缓存层面和入口层面上所发生的操作自动地发出事件通知。

数据网织是支持开放架构的,因此应该有种方法可以从聚合中直接发布领域事件。此时,领域事件可能需要继承框架中的某种事件类型。

有些数据网织支持一种名为持续查询(Continuous Query)的事件通知。客户端可以向数据网织注册一个查询,当对缓存的修改可能影响到查询结果时,客户端将自动接收到事件通知。

数据网织的另一个功能是,它可以在所有复制缓存范围内完成分布式处理,然后将处理结果聚合到一起发给客户端。这使得数据网织可以用于事件驱动的、分布式的并行处理过程中。

微服务设计与拆分的困境

微服务可以解决原来采用集中式架构的单体应用的很多问题,但是如何设计微服务和拆分业务一直是件让人头疼:微服务颗粒度多大?微服务的边界应该在哪里?而且微服务架构模式的提出者Martin Fowler在提出微服务的时候也没有告诉我们究竟如何拆分微服务。微服务设计与拆分困难的根本原因是不知道业务或者微服务的边界到底在哪里。若确定了业务边界和应用边界,微服务设计与拆分的困境也就迎刃而解了。

DDD 解决微服务困境

在战略设计中我们建立了领域模型,划定了业务领域的边界,建立了通用语言和限界上下
文,确定了领域模型中各个领域对象的关系。在此过程中除了完成业务端领域模型的设计工作,也确定了应用端的微服务边界。 这个过程可以分为三步:

第一步:在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。

第二步:根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时
确定聚合中的聚合根、值对象和实体。

第三步:根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成
领域模型

DDD 和微服务的关系

DDD 是一种架构设计方法,微服务是一种架构风格,两者从本质上都是为了追求高响应力,而从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务出发,其核心要义是强调根据业务发展,合理划分领域边界,持续调整现有架构,优化现有代码,以保持架构和代码的生命力,也就是我们常说的演进式架构。

DDD 主要关注:从业务领域视角划分领域边界,构建通用语言进行高效沟通,通过业务抽象,建立领域模型,维持业务和代码的逻辑一致性。

微服务主要关注:运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_12194.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运算放大器积分电路上并联的电阻什么作用

学过模电的同学对运放积分电路应该都不会陌生,基本电路如下图中所示 积分电路主要是用来进行波形变换,放大电路失调的消除,以及反馈控制中的积分补偿。 常用积分电路将方波变幻成三角波,或者正弦波变成余弦波,今天我们…

拍照识别花草软件有哪些?识别植物花草的软件哪个准?

不知道有没有小伙伴和我一样,好奇心比较旺盛,遇到问题都喜欢打破砂锅问到底。就连平时在路上遇到一些好看的花花草草时,我都想知道它是什么。但是花草这些就比较特殊,想了解它的身份,光靠描述可行不通。借助识别工具来…

UEC++ 代理/委托

代理: 代理可以帮助我们解决一对一或是一对多的任务分配工作。主要可以帮助我们解决通知问题。我们可以通过代理完成调用某一个对象的一个函数,而不直接持有该对象的任何指针。代理就是为你跑腿送信的,你可以不用关心给送信的目标人具体是谁…

异步线程使用Request存在问题

概述 如果我们将request传递到异步线程中使用,可能获取不到参数,并且会导致后续的请求,使用到这个线程也会出问题。 原因就是request对象会被重复使用。 源码分析 1、获取参数 先看一个非常重要的方法,getParameter 方法调用第…

计算机毕业设计之java+javaweb的美容院管理系统

计算机毕业设计之javajavaweb的美容院管理系统 项目介绍 系统权限按管理员、用户、医生和美容师这四类涉及用户。 (a) 管理员:进入系统可以实现主页、个人中心、用户管理、医生管理、美容师管理、项目部门管理、项目类型管理、产品分类管理、产品信息管理、医美项目…

JavaEE:进程调度的基本过程

目录 进程是什么? 操作系统对进程的调度 2.1 PCB中的信息 2.2 进程的调度是如何进行的呢? 并行: 并发: 总结: 进程是什么? 如果想了解进程调度的基本过程,我们首先要了解的是进程是什么? 咱们可以在任务管理器中看到 这一切跑起来的程序就是进程! 操作系统对进程的调…

22.this指针

1.this指针工作原理 我们知道,c++的数据和操作也是分开存储,并且每一个非内联成员函数(non-inline member function)只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码那么问题是:这一块代码是如何区分那个对象调用自己的呢?c++通过提供特殊的对象指针,this…

Python面向对象笔记

一、面向对象 (一)基本概念 (1)面向对象编程 —— Object Oriented Programming 简写 OOP (2)面向对象三大特性封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中定义类的准则封装 是面向对象编程的一大特点 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一…

王道-考研-数据结构-线索二叉树

线索二叉树的构造 常用的是中序线索二叉树。 寻找前驱结点:若左指针为线索,则其指向结点为前驱结点。 若左指针为左孩子,则其左子树的最右侧结点为前驱结点。寻找后继结点:若右指针为线索,则其指向结点为后继结点。 若右指针为右孩子,则其右子树的最左侧结点为后继结点。…

Vue支持多文件上传 前端+后端 (详细介绍)

前端vue后端java支持多文件上传效果图Vue部分后台部分效果图 可以上传多个文件 Vue部分 <template><div><el-form-item label"案例名称" prop"caseName"><el-input v-model"formObj.caseName" placeholder"请输入案…

计算机毕业设计之java+javaweb的网上电子书店-图书商城网站

计算机毕业设计之javajavaweb的网上电子书店-图书商城网站 项目介绍 系统权限按管理员和用户这两类涉及用户划分。 (a) 管理员&#xff1a;管理员使用本系统涉到的功能主要有主页、个人中心、用户管理、一级分类管理、二级分类管理、电子书管理、下单购买管理、我的书籍管理、留…

MUR1100-ASEMI快恢复二极管MUR1100

编辑-Z MUR1100在DO-41封装里采用的1个芯片&#xff0c;其尺寸都是50MIL&#xff0c;是一款快恢复二极管。MUR1100的浪涌电流Ifsm为35A&#xff0c;漏电流(Ir)为10uA&#xff0c;其工作时耐温度范围为-55~150摄氏度。MUR1100采用GPP硅芯片材质&#xff0c;里面有1颗芯片组成。…

如何用Vue + Mint UI实现上拉加载更多?

引言: 上拉加载更多在移动端不论是在 app 里面还是在页面中都是必不可少的&#xff0c;以下是 mint-ui 中上拉加载更多的总结。 一、在项目中使用 mint-ui 需要先安装 查看官网 (1)安装:npm i mint-ui --save (2)在 vue 中 main.js 引入 import MintUi from mint-ui import mi…

图扑数字孪生军事营区,实现主动防御

前言 20 世纪 50 年代初中国人民解放军开始自建营区。传统营区管理系统以独立的“点状”系统为主&#xff0c;缺乏集控平台&#xff0c;全局管理复杂度高。70 年代末提出建设智能化营区&#xff0c;并向“数字化、智能化、网络化、互动化、融合化”的方向靠拢。通过建设集光电…

【车辆配送】基于模拟退火 (SA)求解车辆配送 (VPR) (Matlab代码实现)

目录 1 车辆配送问题 2 模拟退火法 3 Matlab代码实现 4 实现结果 5 参考文献 6 写在最后 1 车辆配送问题 式(9)~( 12)中, 为配送车辆到达需求点i的时间;为需求点i到需求点j的运输成本;、分别为配送车辆提前到达需求点i的或者滞后到达需求点i的单位时间内的等待成本以及惩…

C语言编译过程——预处理、编译汇编和链接详解

引言 C语言经典的 “hello world ” 程序&#xff0c;伴随着每个程序员一起步入编程世界的大门。从编写、编译到运行&#xff0c;看到屏幕上输出的“hello world ”&#xff0c;那么你知道它都经历了什么吗&#xff1f;今天我们就来聊聊这个话题。 一、从hello.c聊起 hello …

Java多线程~线程的状态以及状态转移的条件

目录 线程的六种状态 状态转移的条件 NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED 线程的六种状态 线程共有六种状态&#xff0c;分别为&#xff1a; NEW(初始状态)&#xff1a;new表示新建一个线程对象&#xff0c;即安排了工作&#xff0c;但未开始行…

Ubuntu指令说明

1、ls ls命令是list的缩写&#xff0c;用来打印出当前目录的清单。如果ls指定其他目录&#xff0c;那么就会显示指定目录里的文件及文件夹清单。通过ls命令不仅可以查看linux文件夹包含的文件&#xff0c;而且可以查看文件权限&#xff08;包括目录、文件夹、文件权限&#xf…

【逻辑】【java基础】代码逻辑思路 层级关系 【层级注解】【架构逻辑】

命名规范: 层级逻辑关系图: 层级逻辑思路图:(代码架构逻辑)

(附源码)springboot高校宿舍交电费系统 毕业设计 031552

Springboot高校宿舍交电费系统 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运…