聊一聊领域驱动与贫血模型

news/2024/7/6 7:59:58/文章来源:https://www.cnblogs.com/TheGCC/p/18282549

写在前面

前段时间跟领导讨论技术债概念时不可避免地提到了代码的质量,而影响代码质量的因素向来都不是单一的,诸如项目因素、管理因素、技术选型、人员素质等等,因为是技术债务,自然就从技术角度来分析,单纯从技术角度来看代码质量,其实又细分很多原因,如代码设计、代码规范、编程技巧等等,但我个人觉得这些都是技,并不是代码混乱的根,代码之所以混乱的根要从最基础的层面说起,也就是代码架构。

但是好像代码架构也不像是技术选型的产物,它更像是“润物细无声”的环境影响产物,一个Java Web项目,不谈一个企业,甚至整个行业都有一个共识,应该是Controller、Service、Dao那一套,从代码目录结构到内部细节编写,也不知道是MVC架构的宣传深入人心还是培训机构或者企业培训的连带效应导致的这种浑然天成的共识。就是这种浑然天成的共识导致了一个很奇怪的现象,那就是 :使用标榜面向对象的Java语言所开发的项目却十分的不面向对象。这里也不是说面向对象就比面向过程要强,只是各自适用的领域不一样。超过操作系统到应用层级的应用,不论从需求延申角度、系统规模角度、落地实现角度、扩展维护角度,面向对象都要更好一些。

这也是Java这门语言高居榜首的理由。 Java自诞生之初,各自资料、书籍无一不是在讲它的面向对象特性和面向对象设计,随后的领域驱动设计更是面向对象的集成方法论,在这么多buff的加持下,为什么还是会出现使用面向对象去写面向过程的代码呢?答案就在上面的共识里,好像Java项目的开发和贫血模型一直是强绑定的。

严格来讲,概念上领域驱动其实只有一种概念,并没有贫血充血之分,DDD官方概念中并没有明确定义所谓的贫血模式,贫血模式的诞生其实是对于标准领域驱动的简化,而与之对应的标准的DDD就成了充血。

贫血模型

贫血模式很多人不陌生, 即上文提到的传统的Controller、Service、Dao的框架基础之上,配合以Java的对象实体类概念,但实体类仅有属性和属性的get和set方法,在整个系统中,对象几乎只作传输介质的作用,不会影响到层次的划分,业务逻辑多集中在Service中;随之后续的数据库技术、ORM框架等等,都在这一体系上继续垒加,形成了当下最高复制率的JavaWeb项目结构。

还是转账这个经典的例子,使用贫血实现:

/*** 账户业务对象*/
public class AccountBO {/*** 账户ID*/private String accountId;/*** 账户余额*/private Long balance;/*** 是否冻结*/private boolean isFrozen;public String getAccountId() {return accountId;}public void setAccountId(String accountId) {this.accountId = accountId;}public Long getBalance() {return balance;}public void setBalance(Long balance) {this.balance = balance;}public boolean isFrozen() {return isFrozen;}public void setFrozen(boolean isFrozen) {this.isFrozen = isFrozen;}}/*** 转账业务服务实现*/
@Service
public class TransferServiceImpl implements TransferService {@Autowiredprivate AccountMapper accountMapper;@Overridepublic boolean transfer(String fromAccountId, String toAccountId, Long amount) {AccountBO fromAccount = accountMapper.getAccountById(fromAccountId);AccountBO toAccount = accountMapper.getAccountById(toAccountId);/** 检查转出账户 **/if (fromAccount.isFrozen()) {throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);}if (fromAccount.getBalance() < amount) {throw new MyBizException(ErrorCodeBiz.INSUFFICIENT_BALANCE);}fromAccount.setBalance(fromAccount.getBalance() - amount);/** 检查转入账户 **/if (toAccount.isFrozen()) {throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);}toAccount.setBalance(toAccount.getBalance() + amount);/** 更新数据库 **/accountMapper.updateAccount(fromAccount);accountMapper.updateAccount(toAccount);return Boolean.TRUE;}
}

贫血模型的本质

 

贫血模式本质是在架构指导下技术框架趋于成熟的演进产物;换句话说是MVC架构随着技术的发展而不断变体而来。MVC的初衷是一个三层架构,即包含数据结构和业务逻辑的模型Model层、应用程序的用户界面(UI)展示与交互的视图层View、用于协调Model与View的剧中调度层Controller,旨在将数据、逻辑和交互解耦。但伴随着ORM框架、Web框架的进步,慢慢形成了Controller、Service、Dao(ORM)的格局,之后前后端分离的浪潮袭来,导致Controller变成了路由,Dao变成了JDBC的简化,Do变成了数据结构,头和尾都明确定位的夹击下,所有的业务功能只能聚焦于中间的Service里,需求大都集中在了Service,“Service用来承载业务逻辑”的说法更加助长了设计懒惰,形成了 “service=数据+逻辑”的风格,学过C语言的或多或少都听过一句“程序=数据+算法”,所以贫血就是在输入输出都固定框架模式下的面向过程。

贫血模型下的开发特点

贫血模式的service层的定义导致它的底层思维是 “程序=算法+数据” ,与之而来的开发流程或者说特点也就十分明了了。即 功能与数据密不可分、代码与数据密不可分。最后面向数据编程。

 

聚焦功能忽视业务

长期使用贫血模式开发的人员,眼里看到的多是UI原型或者功能,对于系统的全貌,永远是站在功能的角度去描述和了解。往往专注于功能的开发,尽可能地去简化实现,点到实现功能为止,不做过多的设计与业务的扩展思考。实现往往是单维度的,仅仅是为了满足某个功能或某个页面,当需求分析和系统规划不够优秀时,后续功能开发时,不可避免会陷入前后矛盾的情景。

面向数据开发

在接手系统时,程序员一般先看两部分,数据库里的表和功能,最后才是代码,或者接到需求时,首先做的就是根据UI或者产品的描述去设计存储用的表结构,然后再基于表结构去进行代码设计。也就是说,在实现角度,数据库的表结构才是根基。

另一方面就是SQL建模,其实也可以说是重SQL轻代码,绝大多数做Java Web开发地研发人员从根据需求设计表结构、到实现时进行SQL建模十分娴熟,甚至我见过很多高手,能用一个SQL完成功能开发,把功能和数据的关系极尽所能地压缩到一个或者几个SQL中,代码反而只是一个传输媒介。

贫血模式的开发者倾向于将注意力集中在数据上,他们直接在数据表上建模。语言框架更多的功能在于数据库的访问和UI的绘制,虽然语言可能是Java这种完全面向对象语言,但其实应用里并没有什么客观对象,除了数据库的容器和访问者

贫血模型的合理性

说到这好像显得贫血模式一无是处,其实也不绝对,毕竟 “存在即合理”,裁剪了繁重体系的领域驱动后,贫血模式就变得十分轻便,本身面向过程的特点又让它复制性和落地效率十分突出,开发人员只需要UI页面和数据库的表结构,随便一个研发都可以快速接手并完成一个功能的开发 ,降低了软件设计的复杂度,这恰恰和很多开发团队快速响应的诉求不谋而合。

贫血得以盛行的另一个助力就是Spring,Spring家族作为Java系的行业老大哥框架,积累和沉淀非常丰富,各方面已经封装的很全面,所以留出的“自由度”就比较少,它的宗旨就是让开发人员减少重复造轮子,仅仅专注功能的开发就好;这些特点使得Spring本身就带有限制性质,例如Spring的根基Bean管理机制,把对象的管控牢牢把握在框架中,这种将实体类的管理也交由Spring本身也降低了二次开发时面向对象的属性,导致在Spring中进行bean之间的引用改造会面临大范围的bean嵌套构造器的调用问题。所以使用Spring也就大概率默认使用贫血。

贫血模型的问题

不可否认,面对大多数简单而生命周期短暂的项目面向数据开发是一种高效的方式,且当需求发生变动,Service按部就班地追加相应的逻辑也是可以快速响应,可是这些都是建立在系统简单、项目周期短的前提下,一旦面临长生命周期且业务复杂的大型系统,贫血模式的问题就是:

  1. 随着系统体积的增长和需求的蔓延,部分功能设计问题显露,贫血模式下的代码特点难以快速重构来响应这一级别的诉求
  2. 面向过程的形式,让代码陷入 “又臭又长的if else”死胡同,随着不断地开发,维护和改动成本指数级上涨
  3. 长时间的“水多了加面,面多了加水”的开发习惯,团队人员思维固化,难以提出有效地优化或改进,恶性循环

充血模型

充血简单来讲就是OO思想的体系方法论,相比贫血的面向数据,它提出领域的概念,即将业务和数据拆开,对业务部分进行领域划分并进行OOA,代码上更重抽象出的领域实体类,将业务逻辑和判定等内容均内聚到实体类中,Service层仅仅充当组合实体对象的画布,负责简单封装部分业务和事务权限管理等;

为了更直观感受,使用充血模型,对上面的例子进行修改:

/*** 账户业务对象*/
public class AccountBO {/*** 账户ID*/private String accountId;/*** 账户余额*/private Long balance;/*** 是否冻结*/private boolean isFrozen;/*** 出借策略*/private DebitPolicy debitPolicy;/*** 入账策略*/private CreditPolicy creditPolicy;/*** 出借方法* * @param amount 金额*/public void debit(Long amount) {debitPolicy.preDebit(this, amount);this.balance -= amount;debitPolicy.afterDebit(this, amount);}/*** 转入方法* * @param amount 金额*/public void credit(Long amount) {creditPolicy.preCredit(this, amount);this.balance += amount;creditPolicy.afterCredit(this, amount);}public boolean isFrozen() {return isFrozen;}public void setFrozen(boolean isFrozen) {this.isFrozen = isFrozen;}public String getAccountId() {return accountId;}public void setAccountId(String accountId) {this.accountId = accountId;}public Long getBalance() {return balance;}/*** BO和DO转换必须加set方法这是一种权衡*/public void setBalance(Long balance) {this.balance = balance;}public DebitPolicy getDebitPolicy() {return debitPolicy;}public void setDebitPolicy(DebitPolicy debitPolicy) {this.debitPolicy = debitPolicy;}public CreditPolicy getCreditPolicy() {return creditPolicy;}public void setCreditPolicy(CreditPolicy creditPolicy) {this.creditPolicy = creditPolicy;}
}/*** 入账策略实现*/
@Service
public class CreditPolicyImpl implements CreditPolicy {@Overridepublic void preCredit(AccountBO account, Long amount) {if (account.isFrozen()) {throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);}        }@Overridepublic void afterCredit(AccountBO account, Long amount) {System.out.println("afterCredit");}
}/*** 出借策略实现*/
@Service
public class DebitPolicyImpl implements DebitPolicy {@Overridepublic void preDebit(AccountBO account, Long amount) {if (account.isFrozen()) {throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);}if (account.getBalance() < amount) {throw new MyBizException(ErrorCodeBiz.INSUFFICIENT_BALANCE);}}@Overridepublic void afterDebit(AccountBO account, Long amount) {System.out.println("afterDebit");}
}/*** 转账业务服务实现*/
@Service
public class TransferServiceImpl implements TransferService {@Resourceprivate AccountMapper accountMapper;@Resourceprivate CreditPolicy creditPolicy;@Resourceprivate DebitPolicy debitPolicy;@Overridepublic boolean transfer(String fromAccountId, String toAccountId, Long amount) {AccountBO fromAccount = accountMapper.getAccountById(fromAccountId);AccountBO toAccount = accountMapper.getAccountById(toAccountId);//此处采用轻量化地方式解决了自身面向对象和Spring的bean管理权矛盾
        fromAccount.setDebitPolicy(debitPolicy);toAccount.setCreditPolicy(creditPolicy);fromAccount.debit(amount);toAccount.credit(amount);accountMapper.updateAccount(fromAccount);accountMapper.updateAccount(toAccount);return Boolean.TRUE;}
}

充血模型的开发特点

抽象业务

相比于贫血聚焦于功能、UI页面,充血模式在面对设计时,需要更上升一层,聚焦于业务。将业务内容抽象出业务对象、对象关系、业务流程、对象依赖,以排列组合对象来满足业务需求。面对需求,需要层层分析,设计,模型场景推演,做到重业务对象,轻数据,从而实现业务、功能、代码、数据的清晰划分,避免直译式的粘连耦合实现。

通过领域概念将业务与技术分离

领域驱动中突出领域的概念,包含:通用域、支撑域、核心域三部分,其中通用域和支撑域则是纯技术流和通用内容,包括持久化、通信技、安全、性能、通用组件、规则等,核心域则是具体业务分析得出的业务块,其中包含通用的业务语言、领域内的上下文、复杂变化的业务逻辑等。通用域和支撑域负责数据管理、包装接口、绘制界面、收接消息等系统的基本技术能力,以此来支撑核心域的业务流转、运作。 通用域、支撑域不与核心域耦合,让技术归技术、业务归业务,做到技术和业务的分离。

充血模型的问题

很多书籍和资料都是在说充血模型是解决贫血模型的良药,其实也过于吹捧了,之前也提到了,贫血模型有其存在的必要性,充血模型和贫血模型只是适用范围不同,如果贫血模型的缺点都是由于应用场景导致,那充血确是良药。但如果单纯是管理问题、团队人员技术问题导致的问题,那强行套用充血才是灾难,至于贫血换充血的理由,会在后面提到。这里单纯以批评视角说一说充血的问题。

单纯看充血模型存在的问题,就两点:

  • 理论过于理想,实践较为困难
  • 边界难以把控

其实这两点归于一点也不为过,那就是充血模型的实践土壤过于苛刻,因为其脱身于面向对象,着重以面向对象思想解决系统开发中熵增的问题,由此诞生了非常繁重的体系理论,而每一处体系理论都透露着对于团队人员素质以及业务场景理解的高要求;对于团队人员素质,这一点是最难的,因为在一个群体中很难把每个研发对于面向对象的理解拉到同一个维度,只能是不要有太大偏差。其次就是设计者很容易陷入到 “设计师的锤子”理论中,即 “手里有锤子,处处是钉子”,很容易过度设计,导致无意义地加重复杂度,毕竟把握“精准平衡” 这四个字对于 架构师、设计师、研发工程师的要求不是一纸证书那么简单。

从贫血到充血的前制条件

聊怎么做之前,先谈一谈必要性,即贫血一定要切换为充血么,其实从贫血过度到充血也不是必须品,至于要不要使用充血模式,取决于两个方面:

  • 领域特性
  • 团队成熟度

领域特性

衡量是否必要的第一点就是领域逻辑的复杂度,充血模式最适合具有复杂领域逻辑的应用。如果业务比较复杂,随着我们对领域逻辑的了解,就能很快感受到基于数据的做法带来的限制。仅从数据出发,CRUD系统无法创建出好的业务模型。业务的复杂性可以体现在精巧的商业模式、复杂的制造过程、精益的管理方法等方面。

第二点就是领域的稳定性,这里的稳定性是指内在商业逻辑是稳定的,未来的需求主要集中在支撑功能和业务量的扩展上。当然,稳定性指标并不是绝对的,要看它所处的阶段。有的软件功能在时间长度上,几年内将不断变化,但一旦改变就不是简单的变更,例如金融、政策领域的一些法规。或者,虽然业务现阶段处于变化之中,但它的商业模式一旦形成,将作为企业的核心资产,例如新兴的移动互联网应用中的各种商业模式。它们都适合采用DDD,即便业务逻辑处在变化之中,我们也可以从DDD的另一个特性——快速测试和验证领域逻辑中收益,它可以为高频发布类应用提供很好的支持。

团队成熟度

另一个说必要性有点不够准确,应该说是前置条件,那就是团队的成熟度。

团队的成熟度首先表现在团队的技术素养上,如果团队大多数人还需要花费大量时间去学习和体会面向对象技术(这些技术包括UML语言、面向对象设计、理解面向接口编程、基于服务的架构、设计模式、开发流程、需求分析等),那么在构建通用语言、模型设计和架构解耦上投入的精力就会受到限制,冒然转为充血模式的领域驱动,将是灾难性质的。

其次就是团队中一定要有 “领域专家”角色,这个领域专家严格来说只是一种角色,它可以泛指那些对业务领域的政策、工作流程、关键节点和特性都有深刻理解的所有人。一个判断标准是,他们对领域的论述是有体系的,而不是散乱的,而且十分清楚规则的应用范围。没有领域专家,就不会有通用语言和与语言一致的模型和代码。谁来保证我们是“领域驱动”,还是过度设计?即便是最简的设计,谁来验证呢?这个“最简”只能是个伪概念,领域专家的重要性是不可替代的。

说起前置条件不得不提一下项目的周期。相对来说,领域驱动更适合周期长的项目。交付时间过于紧张的项目,团队成员的注意力都会集中在功能的开发上,这时候强调领域知识的学习和领域模型的精炼,显然会和各利益相关者产生工作安排重点的冲突。相反,周期越长的项目,比如核心产品的研发,随着时间的推移,提炼出的领域模型就会逐步释放出它的威力。因此,项目生命周期越长,收益越大。

如何从贫血到充血

主要是思考角度的转换,角度转换主要指两方面:需求视角, 不要过分纠结具体的功能,而是上升一层,去理解业务,抽象出业务对象和它们之间的关系;实现视角不要拘泥于具体代码或实现框架,要站在架构一层上,屏蔽掉系统对于数据的依赖细节,以面向对象的思维去构建系统。

设计视角:将业务转换为对象和对象关系

贫血模式所对应的开发方式更为“简单粗暴”,针对需求和功能页面,1:1的进行技术实现,直接针对功能复刻存储结构、参照MVC,逐层进行编码开发,中间几乎没有分析与设计可言,输出的成果本质上是对功能UI页面负责;这个流程其实是百分百信任或者说依赖产品经理的能力,而产品经理是否在产出产品说明书之前就进行了高质量的需求分析、是否进行了业务到功能的有效映射和转换、是否进行了有效的功能设计又都是研发不可知的。这也是诸多研发人员即便参与工作或者某个项目很久,仍旧无法说清楚所谓“业务”的原因,因为在贫血模式下的开发都是公式化的功能代码转化,没有业务理解和设计。

切入DDD的开发方式,对研发和产品都是挑战,都要有成为领域专家的意识。意味着产品经理角色自己要明白业务、领域的概念,而不是执著于几个功能或者交互;研发人员则需要同产品经理一起参与需求的分析,对于功能的设计和规划不是拘泥于一点,而是根据业务,通过绘制用例图等手段理清整个业务线,搞清楚其中牵扯的对象,并抽象出通用语言和具有业务意义的业务对象,再进一步根据业务流程 去梳理出对象间的关系、对象间的通信,从而得到完整的一套业务模型,最后结合数据情况,设计数据对接部分。

实现视角:切断数据与代码强耦合

所有的模型、被二级制化的领域逻辑和数据都不可能一直活跃在内存中,而需要被存储在数据库或文件中。此时,代码就要与具体的持久化技术框架产生耦合。如果不能将这部分代码分离出去,领域层的独立性就无从谈起了。我们也不可能脱离技术复杂度而独立开发领域逻辑,所以领域模型要想保持自己的独立性,离不开存储库将其与持久化机制解耦。

贫血模式下很多情况是数据库表、SQL模型、ORM配置、实体类到Service逻辑集于一人开发,这就导致了一个问题,当个人拥有“穿透权限”时是很容易忽视边界感的,久而久之就会懈怠去维护层与层之间的边界,业务模型与底层数据通道耦合,难以重构和复用。

解决这种情况技术上就要建立DDD中支撑域的概念,在业务之下,创建真正的数据层,数据层需要反向依赖业务层中定义的需求接口,即数据层是纯技术实现层,实现参照就是各个领域中列出的interface类;对应的开发习惯也需要进行改变,即业务开发人员不要参与数据层的逻辑设计与开发,只需要在Domain中定义数据诉求接口即可,由专门的开发团队去完成数据层或者说数据底座的开发,这样能够保证边界感,同时也能防止代码泛滥。通过支撑域数据层和核心域业务层的依赖倒置,实现代码上的领域模型与数据的解耦。保证业务模型能够在不受底层技术影响的情况下进行演化,让我们可以独立开发领域模型,无须关注架构的技术细节。

架构视角补充:利用领域概念进行技术整合

 

使用DDD概念后,类似于包装接口、描绘界面、参数校验、数据封装、各类Util工具包, 包括上文中提到的支撑域中的数据管理则都可以划分到纯技术中,对于体量较大的系统,核心领域则可以拆分为单独的module,配合整个平台统一的数据支撑域module,形成模块化组装形态,各个领域不依赖数据形态、数据获取方式,仅提出数据接口需求,由支撑域完成核心域的需求,技术角度上,支撑域是一个大而全的数据底座,依赖于业务领域绘制的需求进行开发。

对于微服务架构,DDD更多的是利用其领域划分提供服务划分依据,技术上为保证各服务的独立性,需要弱化支撑域的概念,转而技术上将权限下放给各个服务,独立的各个微服务内部可以有自身独立的数据交互、数据持久化通道,保证各微服务的独立管理和演进,将支撑域中的内容抽离为通用域的公共组件,进行技术上的统一化管理。

 

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

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

相关文章

《智能计算系统》第五章 编程框架原理(上)课程笔记

《智能计算系统》第五章 编程框架原理(上)课程视频链接:https://www.bilibili.com/video/BV1Ei421i7Rg 本文源自于B站国科大计算所 智能计算系统课程官方账号 所公开上传的视频,在原有视频之上,提取了关键帧、将音频转成了文字并进行了校正,以便学习使用。在此,也感谢国…

点云分割网络---Point Transformer V2

PDF: 《Point Transformer V2: Grouped Vector Attention and Partition-based Pooling》 CODE: https://github.com/Gofinge/PointTransformerV2 一、大体内容 前面一篇文章介绍了Point Transformer,这一篇在其基础上进行改进,提出了强大且高效的Point Transformer V2模型,…

【坚果识别】果实识别+图像识别系统+Python+计算机课设+人工智能课设+卷积算法

一、介绍 坚果识别系统,使用Python语言进行开发,通过TensorFlow搭建卷积神经网络算法模型,对10种坚果果实(杏仁, 巴西坚果, 腰果, 椰子, 榛子, 夏威夷果, 山核桃, 松子, 开心果, 核桃)等图片数据集进行训练,得到一个识别精度较高的模型文件,让后使用Django搭建Web网页端…

MRCTF 2022 EzJava

MRCTF 2022 EzJava 题目分析 下载附件得到一个 jar 包和一个 waf 配置文件。如果只是为了本地搭建环境,直接启动 jar 包就行了,但是如果需要进行远程调试就需要进行一些配置(这个网上教程很多),这个调试也要看具体需求,能直接打通的话就不需要调试。 但是不管怎么说,第一…

逻辑回归求解二分类问题以及SPSS的实现

分类问题就是给出物质的属性,判断其属于什么成分,本文将讲述逻辑回归求解二分类问题 本文着重于模型的实现,对于推导只是概括性的叙述 目录一、问题提出二、逻辑回归函数logistic1.线性线性概率模型2.sigmod函数3.求解方法————极大似然估计4.分类原则三、SPSS实现———…

线上的一次fullgc排查过程

线上服务的GC问题,是Java程序非常典型的一类问题,非常考验工程师排查问题的能力。同时,几乎是面试必考题,但是能真正答好此题的人并不多,要么原理没吃透,要么缺乏实战经验。过去半年时间里,我们的广告系统出现了多次和GC相关的线上问题,有Full GC过于频繁的,有Young G…

基于GWO灰狼优化的LDPC码NMS译码算法最优归一化参数计算和误码率matlab仿真

1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印):2.算法涉及理论知识概要LDPC码是一种线性错误修正码,以其接近香农极限的优良性能而被广泛应用于现代通信系统中。NMS译码是一种基于最小平方误差准则的软判决译码方法,其目标是找到一个最可能的码字,使得接…

k8s-核心组件

核心组件组成 Kubernetes 主要由以下几个核心组件组成: - etcd :保存整个集群的状态 - API Server:提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制 - Controller Manager:负责维护集群的状态,如故障检测、自动扩展、滚动更新等 - Scheduler:…

学习笔记485—Excel技巧:一键将文本数字转换为数值

Excel技巧:一键将文本数字转换为数值在使用Excel进行数据处理时,经常会遇到数据格式不匹配的问题。特别是当从外部导入数据或手动输入数据时,数字可能会被误识别为文本格式,这在进行数据计算和分析时会带来诸多不便。幸运的是,Excel提供了一些便捷的方法,可以帮助我们一键…

使用IDEA给项目打jar包

使用IDEA给项目打jar包参考地址:https://www.cnblogs.com/blog5277/p/5920560.html 感谢作者一、准备一个Java项目并先看看效果 ​ 只想打包,就跳过这一步: 一、准备一个Java项目并先看看效果。 ​ 如果启动jar包遇到报错:无法找到/加载主类就去看看第二步开头。 1、找一…

MCU点灯

MCU点灯 芯片型号:STM32F407ZET6 4个LED灯,网络标号分别为LED0 ,LED1,FSMC D10,FSMC D11。对应的引脚号分别为PF9,PF10,PE12,PE13。原理图//1.定义变量 GPIO_InitTypeDef GPIO_InitStructureF;//F端口 GPIO_InitTypeDef GPIO_InitStructureE;//E端口 int main()//中文注释 …

电子计算机类比赛的“武林秘籍”-电赛光电设计大赛计算机设计大赛嵌入式芯片与系统设计竞赛,你要的都在这里!

本文主要介绍了工科类学生参加比赛的必要性和益处、电子计算机类比赛的基本思路等内容电子计算机类比赛的“武林秘籍”-电赛光电设计大赛计算机设计大赛嵌入式芯片与系统设计竞赛,你要的都在这里! 为什么需要参加电子计算机类比赛 对于实现短期目标而言: 电子计算机类学科竞…

VMware安装CentOS7环境

准备 CentOS7的iso镜像 下载链接:https://mirrors.aliyun.com/centos/7/isos/x86_64/配置步骤 步骤一——创建虚拟机 1、点击创建新的虚拟机2、选择典型3、选择镜像文件4、填写对应信息5、选择虚拟机存储的位置6、存储为单个文件7、创建步骤二——配置centos7 1、打开虚拟机后…

Mybatis执行器

mybatis执行sql语句的操作是由执行器(Executor)完成的,mybatis中一共提供了3种Executor:类型 名称 功能REUSE 重用执行器 缓存PreparedStatement,下一次执行相同的sql可重用BATCH 批量执行器 将修改操作记录在本地,等待程序触发或有下一次查询时才批量执行修改操作SIMPLE…

本地安装seata

1. 下载,解压steata安装包 2. 修改配置里面的端口号 只要是localhost或者不是自己的端口号都更改成自己的端口号3. 修改配置nacos的文件执行 4、数据库中添加对应的表 数据库中的表名称尽量位seata_server,避免后面去修改 5、线上nacos配置 在public 下面进行配置,修改自己对…

关于自定义unordered_set\unordered_map中Hash和KeyEqual:函数对象和lambda表达式简单应用

以unordered_set为例,首先在cppreference中查看其模板定义:可以看到Hash类默认是std::hash<Key,KeyEqual类似,本文将Hash以函数对象写出,将KeyEqual以lambda写出。 class hashvec{public:size_t operator()(const vector<int> & vec) const {return hash<…

Python预测体重变化:决策树、tf神经网络、随机森林、梯度提升树、线性回归可视化分析吸烟与健康调查数据

全文链接:https://tecdat.cn/?p=36648 原文出处:拓端数据部落公众号 在当今的数据驱动时代,机器学习算法已成为解析复杂数据集、揭示隐藏模式及预测未来趋势的重要工具。特别是在医疗健康领域,这些算法的应用极大地提升了我们对疾病预防、诊断及治疗方案的理解与制定能力。…

Nuxt3 的生命周期和钩子函数(九)

摘要:本文介绍了Nuxt3中与Vite相关的五个生命周期钩子,包括vite:extend、vite:extendConfig、vite:configResolved、vite:serverCreated和vite:compiled,展示了如何在每个钩子中扩展Vite配置、读取配置、添加中间件和处理编译事件。每个钩子都有详细的描述和示例代码,帮助开…

地理信息科学:生态保护的智慧经纬

在地球这颗蓝色星球上,每一片森林的呼吸、每一条河流的流淌,都是生命交响曲中不可或缺的音符。而地理信息科学(GIS),正是我们手中解读自然密码、护航生态平衡的精密仪器。今天,让我们深入探讨GIS如何在生物多样性保护和生态系统管理中发挥其不可替代的作用。 🌱 GIS——…

软连接与硬链接

(1)软链接(symbolic link) 创建命令:ln -s <target> <link_name>其中:<target> 是目标文件或目录的路径,可以是相对路径或绝对路径。<link_name> 是要创建的软链接的名称,可以是相对路径或绝对路径。特点:笔试面试填空题和简答题:软链接的特点…