软件设计不是CRUD(7):低耦合模块设计实战——组织机构模块(中)

======== 接上文《软件设计不是CRUD(6):低耦合模块设计实战——组织机构模块(上)》

组织机构功能是应用系统中常见的业务功能之一,但是不同性质、不同行业背景、不同使用场景的应用系统对组织机构功能的要求可能完全不一样。所以使用这样的功能对低耦合模块设计进行示例性的讲解是比较具有代表性的。在后续的几篇文章中,我们会首先进行示例的详细讲解,然后再基于这个示例进行理论讲解。

4、做一个默认实现

在这里插入图片描述

如上图所示,由于组织机构模块可以有多种实现方式;例如可以研发一套基于本地关系型数据库的实现,也可以研发一套基于远程调用的实现,还可以研发一套基于ES搜索引擎的实现。这里本文来演示一套基于本地关系型数据库的实现,这套实现作为产品研发团队提供的默认组织机构实现提供给上层项目团队/二次开发团队使用(由于篇幅有限,本文尽可能演示其中的关键代码),二次开发团队如果不满意这套实现,可以基于这套实现进行调整也可以对这套实现进行整体替换,但都不会影响其它模块对该模块的调用——因为其它“可以看到”组织机构的模块,依赖的都是组织机构模块提供的接口。

4.1、对接口的补充说明并建立工程脚手架

如上图所示,组织机构模块基于本地关系型数据库的实现,工程名被定为simple-org-local(pom文件中定义为(simple-org-local-starter),使用者可以通过在应用程序POM文件中设定simple-org-local-starter依赖的方式,将组织机构模块基于本地数据库的实现引入到应用程序中运行。另外要说明的是,笔者在实现过程中针对之前SDK设计考虑不周的问题,对组织机构接口做了细微调整,在进行正式介绍前,需要向读者先说明这些细微调整:

  • 在OrganizationModuleRegister接口和UserMappingModuleRegister接口中,增加了返回具体模型类型的方法。这是因为在实现过程中发现如果二次开发团队需要依赖当前的默认实现调整一些模型的字段情况,则需要提供一个指定具体模型类的方法:
// 对OrganizationModuleRegister接口进行的调整
public interface OrganizationModuleRegister <O extends Organization> extends Ordered {
  // ......
  // 该方法用于返回具体要转换的组织机构class类型
  public Class<? extends O> getOrgClass();
  // ......
}

// 对UserMappingModuleRegister接口进行的调整
public interface UserMappingModuleRegister <M extends UserMapping> extends Ordered {
  // ......
  // 该方法用于返回具体要转换的组织机构-用户class类型
  public Class<? extends M> getMappingClass();
  // ......
}
  • 提供支持简单列表结构的组织机构模型。这是因为在实现过程中发现有的项目团队可能不需要组织机构支持树形结构,只需要提供对单纯二维表结构的支持。最简单的方式就是将组织机构模型,分为两个有继承关系的独立接口:
// Organization模型接口不具有树形结构的描述特点
public interface Organization {
  // 组织机构类型
  public String getType();
  // 在组织机构类型下,唯一的组织机构业务编码
  public String getCode();
  // 组织机构的中文名
  public String getName();
}

// ...................

// 继承他的TreeOrganization模型接口,才具有描述树形结构的要求
public interface TreeOrganization extends Organization {
  // 组织机构携带的下级组织机构信息(组织机构特性字段)
  public <O extends Organization> List<O> getKids();
  public void setKids(List<? extends Organization> kids);
  // 组织机构直接携带的人员绑定信息(组织机构特性字段)
  public <M extends UserMapping> List<M> getUsers();
  public void setUsers(List<? extends UserMapping> users);
  // 获取当前节点可能的父级节点类型(注意:可能没有)
  public String getParentType();
  // 获取当前节点可能的父级节点编号(注意:可能没有)
  public String getParentCode();
}

以上调整都是对上文介绍的组织机构接口的细微调整,都不会影响读者对于实现的理解,也不会影响本示例所要体现出的低耦合设计思想。接着我们就可以进入正题了,首先给出这个本地数据库实现的具体脚手架(以及脚手架中重要的实现类),然后再对脚手架中重要的实现类进行详细介绍。
在这里插入图片描述
其它一些关注度不会太高的代码包还有基于JPA的数据库模型entity包,以及数据库操作接口repository包,还有工具包utils(实际工作中,这些工具包会统一放置在一些更下层的模块中)等等。读者可以在作者的下载空间中,对这些详细代码进行下载。

TODO 代码还没有上传

注意:spring.factories文件的设置方式是spring-boot 2.7 之前版本的设置方式,在spring-boot 2.7及后续版本推荐/要求使用org.springframework.boot.autoconfigure.AutoConfiguration.imports文件的设置方式。读者自行进行脚手架验证时,需要注意这个细节。

4.2、建立和注册具体的业务模型

上文已经多次提到,在组织机构模块的接口定义中,关于“组织机构具体的模型结构”这件事情,只是进行了一个抽象描述,既是:只要具有类型、类型下唯一编号的模型,就可以是一种组织机构(实现Organization接口)。如果这种组织机构需要支持树形接口,则另外需要规定父级组织机构的信息和下级组织机构的信息(实现TreeOrganization接口)。

那么在具体的实现中就需要对组织机构的模型结构进行详细描述了,由于在这个默认实现中,组织机构类型是需要支持树形结构的,所以需要实现TreeOrganization接口:
在这里插入图片描述
DefaultOrg类的详细代码,如下所示:

/**
 * 这是标准产品提供的默认组织机构实现,其中默认可以关联各种类型的下级组织机构和各种类型的用户
 * 为了简化代码,这里使用了lombok组件,以降低不必要的代码
 * @author yinwenjie
 */
@Getter
@Setter
public class DefaultOrg implements TreeOrganization {
  public static final String DEFAULT_ORG = "default";
  private String id;
  // 组织机构类型
  private String type = DEFAULT_ORG;
  // 业务编号
  private String code;
  // 组织机构上级组织机构编号
  private String parentCode;
  // 组织机构上级组织机构类型
  private String parentType;
  // 中文名
  private String name;
  // 组织机构完整称呼(组织机构特性字段)
  private String mainName;
  // 组织机构邮件地址(组织机构特性字段)
  private String mail;
  // 组织机构电话信息(组织机构特性字段)
  private String phone;
  // 组织机构携带的下级组织机构信息(组织机构特性字段)
  private List<? extends Organization> kids;
  //组织机构直接携带的人员绑定信息(组织机构特性字段)
  private List<? extends UserMapping> users;
}

当然,除了默认组织机构的详细结构描述外,组织机构的默认实现中还实现了一个默认的用户关联信息。该关联信息的数据结构,实现了UserMapping接口,代码如下所示:

/**
 * 这是默认的组织机构、用户关联信息
 * (实际上生产系统的情况下,这个关联用户结构应该由上层用户模块进行定义)
 * @author yinwenjie
 */
@Getter
@Setter
public class DefaultMappingUser implements UserMapping {
  public static final String DEFAULT_MAPPING_ORG = "default";
  // 唯一的用户账号信息
  private String account;
  // 用户中文姓名信息
  private String describer;
  // 用户昵称信息
  private String name;
  // 用户年龄(特异性业务字段)
  private Integer age;
  // 用户检查次数信息(特异性业务字段)
  private BigDecimal checkPoint;
  // 业务申请次数(特异性业务字段)
  private Integer requestNum;
}

注意事项:类似的这种用户关联信息,在实际工作基本不会放置在本模块的实现中,而是直接放置在需要和组织机构信息建立关联的上层模块中(例如上层的管理员用户模块、监控员用户模块),这样做有几个原因:

  • 重用上层模块的模型结构代码:上层模块中需要和组织机构信息建立关联的信息结构,只要直接实现UserMapping接口,就可以将模型结构关联到组织机构模块中,不需要上层模块做额外的代码增加,也不会发生因为上层模块需要将信息关联到组织机构,而改变自身模型结构的情况。

  • 将关联关系的具体实现交给上层模块,保证在上层模块对下层模块透明的情况下,支持更多关联模块的扩展:本系列文章中多次提到,为了保证系统中模块分层的稳定性,上层模块是对下层模块透明的。换句话说下层模块是不知道其上层有什么模块,所以下层模块当然无法知晓上层有哪些模块需要建立和组织机构的关联。
    在这里插入图片描述

以下代码是具体的组织机构模型和用户关联模型的注册实现(DefaultOrg和DefaultMappingUser两个模型):

// 注册组织机构模型(默认组织机构)
public class DefaultOrganizationModuleRegister implements OrganizationModuleRegister<DefaultOrg> {  
  // 默认的组织机构类型,排序优先度很高
  @Override
  public int getOrder() {
    return 1;
  }
  @Override
  public String type() {
    return DefaultOrg.DEFAULT_ORG;
  }
  // 这里描述json结构到具体DefaultOrg模型的转换
  @Override
  public DefaultOrg transform(JSONObject json) {
    // ......
  }
}

// =============
// 注册示例中需要的组织-用户关联模型
public class DefaultUserMappingModuleRegister implements UserMappingModuleRegister<DefaultMappingUser> {
  // 默认这个模型注册器所转换的对象模型,排序优先级最高
  @Override
  public int getOrder() {
    return 0;
  }
  // 具体描述json结构和用户关联信息的转换
  @Override
  public List<DefaultMappingUser> transform(JSONArray users) {
    // 只有要求转换的用户关联信息,其type都为default的时候,才进行转换,否则报错
    // ......
  }
}

4.3、建立具体的业务行为

建立了具体的业务模型后,现在需要进行具体的业务行为描述。业务行为在本示例中可以简单理解为对业务模型的增、删、改、查操作。这就是组织机构模块中,OrganizationStrategy接口和UserMappingStrategy接口存在的原因。

在这里插入图片描述
注意:以上两个策略接口中的行为只是需要接入组织机构模块统一控制的各种模型行为,不包括某种组织机构类型、某种组织机构-用户关联类型特有的行为。例如,作为组织机构模型来说,一定存在创建、修改和层次结构查询的要求,这些属于对于组织机构模型最基本的要求。另外,不同的组织机构类型还有定制化的行为,例如“组织机构类型A”还允许进行临时禁用操作。而这些针对不同组织机构类型的定制化行为,并不属于OrganizationStrategy接口和UserMappingStrategy接口的控制范围,需要二次开发团队根据自己的需要,自行定义并实现(一般在Service层定义新的接口,且这些接口和实现只存在于项目使用的范围)。

  • 首先是默认组织机构的业务逻辑描述
public class DefaultOrganizationStrategy implements OrganizationStrategy<DefaultOrg> {
  // 默认实现内部使用的数据持久层功能
  @Autowired
  private DefaultOrgRepository orgRepository;
  @Override
  public String type() {
    return DefaultOrg.DEFAULT_ORG;
  }
  // 创建默认组织机构信息
  @Override
  public DefaultOrg create(DefaultOrg org) {
    /*
     * 这里进行默认组织机构的实际创建,过程为:
     * 1、进行和业务相关的边界校验
     * 2、进行数据层的保存
     * 注意,这里不进行监听器的激活,因为它不属于实际的创建过程
     * */
    
    // 1、=============
    // 验证和业务相关的字段
    String mail = org.getMail();
    Validate.notBlank(mail , "创建时,邮件信息必须填写");
    // TODO 还能进行邮件字段的其它业务相关性验证,例如邮件是否合规,是否被使用等
    String name = org.getName();
    Validate.notBlank(name , "创建时,姓名信息必须填写");
    String phone = org.getPhone();
    Validate.notBlank(phone , "创建时,电话信息必须填写");
    
    // 2、=============
    // 转换成默认实现内部使用的数据持久层对象,并进行入库操作
    DefaultOrgEntity orgEntity = this.transform(org);
    this.orgRepository.save(orgEntity);
    // 保存后,数据层会生成一个id
    org.setId(orgEntity.getId());
    return org;
  }
  
  // ...... 其它方法实现过程略
  // 包括必要的查询、修改操作等
}
  • 然后是默认组织机构-用户映射方式的业务逻辑描述
public class DefaultUserMappingStrategy implements UserMappingStrategy<DefaultMappingUser> {
  // 默认实现内部的数据层功能
  @Autowired
  private DefaultMappingUserRepository defaultMappingUserRepository;
  
  @Override
  public int getOrder() {
    return 1;
  }
  @Override
  public String type() {
    return DefaultMappingUser.DEFAULT_MAPPING_ORG;
  }
  // 创建新的组织机构-用户映射信息
  @Override
  public DefaultMappingUser create(DefaultMappingUser userMapping) {
    // 1、========== 边界校验
    Integer age = userMapping.getAge();
    Validate.notNull(age , "创建关联信息时,人员年龄信息必须填写");
    // TODO 当然还可以进行更多的正确性验证
    Integer requestNum = userMapping.getRequestNum();
    Validate.notNull(requestNum , "创建关联信息时,人员申请次数必须填写");
    
    // 2、========== 进行数据层模型转换
    DefaultMappingUserEntity userMappingEntity = this.transform(userMapping);
    this.defaultMappingUserRepository.save(userMappingEntity);
    userMapping.setId(userMappingEntity.getId());
    return userMapping;
  }
  
  // ...... 这里有一些私有方法和其他业务行为
  
  @Override
  public Collection<DefaultMappingUser> queryUsers(String parentType, String parentCode) {
    // 进行边界校验后,就进行数据库查询
    if(StringUtils.isAnyBlank(parentType , parentCode)) {
      return Lists.newArrayList();
    }
    List<DefaultMappingUserEntity> userMappingEntities = this.defaultMappingUserRepository.findByOrgTypeAndOrgCode(parentType, parentCode);
    if(CollectionUtils.isEmpty(userMappingEntities)) {
      return Lists.newArrayList();
    }
    
    // 如果存在查询结果,则转换为对应的业务模型,然后进行输出
    List<DefaultMappingUser> mappingUsers = this.transform(userMappingEntities);
    return mappingUsers;
  }
}

注意:和UserMapping接口、UserMappingModuleRegister接口类似,在实际工作中对于UserMappingStrategy接口的实现也会交给应用系统中处于组织机构模块上层的,需要接入组织机构模块建立组织机构-用户关联的模块进行实现。出现这种情况原因在上文中已经介绍过了,这里不再赘述。
在这里插入图片描述

4.4、将业务行为进行间接耦合

在《软件设计不是CRUD(5):耦合度的强弱(下)》文章的描述中,我们对间接耦合的概念进行了详细描述。总结来说,就是模块内部没有业务过程,只负责按照一定的逻辑将业务过程组装起来,且处理逻辑本身是可以变化。

为了使模块的耦合强度下降到间接耦合,我们需要在模块中设计一个描述业务过程组装方式的中间层,让每个业务逻辑能够按照一定的工作顺序运行起来(称为处理逻辑)。在本示例中,我们选择传统编程结构中的Service层作为这个处理逻辑的描述层。

public class OrgServiceImpl implements OrgTreeService {
  // 这里是系统中已经注册的组织机构行为
  @Autowired(required = false)
  private List<? extends OrganizationStrategy<? super Organization>> organizationStrategies;
  // 这里是系统中已注册的组织机构-用户关联行为
  @Autowired(required = false)
  private List<? extends UserMappingStrategy<? super UserMapping>> userMappingStrategies;
  // 这里是系统中已注册的组织机构模型具体的类型描述
  @Autowired(required = false)
  private List<? extends OrganizationModuleRegister<? super Organization>> organizationModuleRegisters;
  // 这里是系统中已注册的需要监听组织机构模块事件变化的具体监听器
  @Autowired(required = false)
  private List<? extends OrgListener> orgListeners;
  
  @Override
  @Transactional
  public Organization create(Organization org) {
    /*
     * 在进行新的组织机构查询时,该service方法中并没有具体的业务逻辑过程
     * 而是描述了一个控制过程,具体的处理逻辑在OrganizationStrategy接口的某个具体实现中进行实现
     * 
     * 1、进行边界校验,只对必要的类型信息进行校验,因为处理类型信息以外,本控制过程不需要关注其它业务字段的校验问题
     * 2、通过类型信息找到对应的处理策略,并检查节点是否已经存在,然后进行正式创建操作
     * 3、进行了组织机构基本信息的添加后,再根据情况确认是否要进行携带的用户关联信息的添加
     * 4、创建成功后,本处理逻辑会触发事件监听,将创建成功后的状态通知到上层需要知晓该模块数据变化的模块
     * */
    
    // 1、================
    Validate.notNull(org , "创建时,需要传入必要的组织机构信息");
    String type = org.getType();
    Validate.notBlank(type , "创建时,必须传入组织机构类型(type)信息");
    String code = org.getCode();
    Validate.notBlank(code , "创建时,必须传入组织机构编号(code)信息");
    Validate.isTrue(!CollectionUtils.isEmpty(organizationStrategies) , "创建时,未发现任何已注册的处理方式");
    // 检验可能存在的父级节点信息
    if(org instanceof TreeOrganization) {
      TreeOrganization treeOrg = (TreeOrganization)org;
      String parentCode = treeOrg.getParentCode();
      String parentType = treeOrg.getParentType();
      OrganizationStrategy<? super Organization> parentOrganizationStrategy = this.findOrganizationStrategy(parentType);
      Validate.isTrue(!StringUtils.isAllBlank(parentCode , parentType) && !StringUtils.isAnyBlank(parentCode , parentType) 
        , "创建时,要么父级信息全部完整填写;要么都不填写");
      // 只有两者都完整,才任务当前节点设定了父级节点,如果有父节点,则要确认父节点
      if(!StringUtils.isAnyBlank(parentCode , parentType)) {
        Organization parentOrganization = parentOrganizationStrategy.queryByCode(parentType, parentCode);
        Validate.notNull(parentOrganization , "无法找到指定的父级节点,请检查");
      }
    }
    
    // 2、================
    // 找到正确的操作策略器,并进行正式的创建处理
    OrganizationStrategy<? super Organization> currentOrganizationStrategy = this.findOrganizationStrategy(type);
    Validate.notNull(currentOrganizationStrategy , "创建时,未发现正确的已注册的处理方式(%s)" , type);
    // 验证要添加的节点,是否已经存在
    Organization exsitOrg = currentOrganizationStrategy.queryByCode(type, code);
    Validate.isTrue(exsitOrg == null , "创建时,发现指定类型下的组织机构编号已经被使用,请检查");
    Organization result = currentOrganizationStrategy.create(org);
    Validate.notNull(result , "创建成功后的结果不能为空");
    
    // 3、================
    if(org instanceof TreeOrganization) {
      TreeOrganization treeOrg = (TreeOrganization)org;
      List<? extends UserMapping> users = treeOrg.getUsers();
      if(!CollectionUtils.isEmpty(users)) {
        for (UserMapping userMapping : users) {
          String userMappingType = userMapping.getType();
          Validate.notBlank(userMappingType , "创建时,未传入用户映射类型信息");
          UserMappingStrategy<? super UserMapping> currentUserMappingStrategy = this.findUserMapping(userMappingType);
          Validate.notNull(currentUserMappingStrategy , "创建时,未找到正确的用户映射类型处理策略");
          currentUserMappingStrategy.create(userMapping);
        }
      }
    }
    
    // 4、================
    // 一旦创建成功,则事件会被触发
    if(!CollectionUtils.isEmpty(orgListeners)) {
      for (OrgListener orgListener : orgListeners) {
        orgListener.onCreated(result);
      }
    }
    return result;
  }
  
  // ......
  // 还实现了诸如树形结构查询的方法,请下载代码自行阅读
  // ......
}

从以上的实现代码来看,代码中不是描述某一种具体的组织机构类型应该怎么进行业务属性相关的边界校验、新增入库或者字段修改,而是描述了一种处理过程:先进行业务无关的边界校验,再寻找正确的组织机构处理策略并调用具体的创建过程,然后再调用正确的组织机构-用户映射创建过程,最后触发事件监听。这样一来,定义不同的类型的组织机构和不同类型的组织机构-用户映射关系,就可以基于两个不相交的“组织机构类型”维度和“组织机构-用户映射类型”维度形成无数多条业务逻辑分支。

另外需要注意的是,在本示例中我们将控制逻辑放置在组织机构本地数据库实现(的脚手架)中,主要原因是为了方便讲解顺序。实际的工作中,类似这种控制逻辑的具体实现类(OrgServiceImpl)一般会被放置在接口定义的脚手架中(这里就是org-sdk),因为这种控制逻辑和技术人员做那种具体的实现是没有关系的。
在这里插入图片描述

  • 为了便于读者理解这种控制耦合,上图将组织机构中两个不相交的维度映射成X-Y轴的平面坐标系,并进行控制逻辑的相交表述——但实际上这并不是“维度相交”的定义,只是为了便于读者进行阅读理解才这样进行坐标系映射。后文中,我们将详细描述什么叫做业务维度相交、

  • 因为间接耦合在本示例中是通过一套固定的逻辑过程被关联上的,所以有一种可能是由于这套逻辑过程相对于使用方出现了很大差异,使用方需要直接改变这套固定的处理逻辑过程。这种情况当然是可能出现的,解决办法也只有推荐使用方重新替换service层的实现(只是逻辑过程无法满足的情况),或者直接替换整个组织机构模块的实现(逻辑过程、业务维度都无法满足的情况)。这种场景一般认为组织机构的业务屈服度未达到设计期望,关于业务屈服度的介绍将在后文进行展开。

在这里插入图片描述

上图我们汇总了目前组织机构模块的接口定义和本地数据库默认实现。后文我们将介绍,这个组织机构默认的本地数据库实现,如果部署到具体的应用中。二次开发团队怎么针对组织机构模块提供的接口定义,进行二次开发工作。

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

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

相关文章

Sprint Boot 3.0

1. 简介 视频教程特点&#xff1a; Spring Cloud带动了Spring BootSpring Boot成就了Spring Cloud

“创未来,享非凡“ 昇腾AI开发者创享日广州站圆满成功

在羊城广州的科技新风潮中&#xff0c;一个以创新为核心、以智能为驱动的盛会在这座南国明珠城市如火如荼地展开。这不仅是一场技术的盛宴&#xff0c;更是人工智能产业发展动力的一次集结。 12月9日&#xff0c;在广州市工业和信息化局的倡导下&#xff0c;一场主题为“创未来…

大数据Doris(三十五):Unique模型(唯一主键)介绍

文章目录 Unique模型(唯一主键)介绍 一、创建doris表 二、插入数据

为 Compose MultiPlatform 添加 C/C++ 支持(2):在 jvm 平台使用 jni 实现桌面端与 C/C++ 互操作

前言 在上篇文章中我们已经介绍了实现 Compose MultiPlatform 对 C/C 互操作的基本思路。 并且先介绍了在 kotlin native 平台使用 cinterop 实现与 C/C 的互操作。 今天这篇文章将补充在 jvm 平台使用 jni。 在 Compose MultiPlatform 中&#xff0c;使用 jvm 平台的是 An…

京东数据运营(京东API接口):10月投影仪店铺数据分析

鲸参谋监测的京东平台10月份投影仪市场销售数据已出炉&#xff01; 10月份&#xff0c;环同比来看&#xff0c;投影仪市场销售均上涨。鲸参谋数据显示&#xff0c;今年10月&#xff0c;京东平台投影仪的销量为16万&#xff0c;环比增长约22%&#xff0c;同比增长约8%&#xff1…

免费分享一套Springboot+Vue前后端分离的在线商城系统,挺实用的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringbootVue前后端分离的在线商城系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringbootVue在线商城系统 毕业设计 Java毕业设计_哔哩哔哩_bilibili【免费】springbootvue在线商城系统 毕业设计 …

六何分析法分析uniApp

一、什么是 uniApp&#xff08;What&#xff09; uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布iOS、Android、H5、以及各种小程序( 微信/支付宝/百度/头条/00/钉钉/淘宝)、快应用等多个平台。uni-app 在手&#xff0c;…

【蜗牛到家】获南明电子信息产业引导基金战略投资

智慧社区生活服务平台「蜗牛到家」已于近期获得贵阳南明电子信息产业引导基金、华科明德战略投资。 贵阳南明电子信息产业引导基金属于政府旗下产业引导基金&#xff0c;贵州华科明德基金管理有限公司擅长电子信息产业、高科技产业、城市建设及民生保障领域的投资&#xff0c;双…

TCP的滑动窗口机制

网络的错误检测和补偿机制非常复杂。 一、等待超时时间&#xff08;返回ACK号的等待时间&#xff09; 当网络繁忙时会发生拥塞&#xff0c;ACK号的返回变慢&#xff0c;较短的等待时间会导致频繁的数据重传&#xff0c;导致本就拥塞的网络雪上加霜。如果等待时间过长&#xf…

如何一个例子玩明白GIT

一个例子玩明白GIT GIT的介绍和教程五花八门&#xff0c;但实际需要用的就是建仓、推送、拉取等操作&#xff0c;这儿咱可以通过一个例子熟悉这些操作&#xff0c;一次性搞定GIT的使用方法学习。下面这个例子的内容是内容是建立初始版本库&#xff0c;然后将数据复制到 "远…

【Linux】第二十七站:内存管理与文件页缓冲区

文章目录 一、物理内存和磁盘交换数据的最小单位二、操作系统如何管理内存三、文件的页缓冲区四、基数树or基数&#xff08;字典树&#xff09;五、总结 一、物理内存和磁盘交换数据的最小单位 我们知道系统当中除了进程管理、文件管理以外&#xff0c;还有内存管理 内存的本质…

【数据结构】面试OJ题———栈|队列|互相实现|循环队列|括号匹配

目录 1. 有效的括号 思路&#xff1a; 2.用队列实现栈 思路&#xff1a; 3.用栈实现队列 思路&#xff1a; 4.设计循环队列 思路&#xff1a; 1. 有效的括号 20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 给定一个只包括 (&#xff0c;)&#xff0c;{&…

Gazebo 跟踪8字形和U形轨迹(1) — 错误处理

Gazebo 跟踪8字形和U形轨迹(1) — 错误处理 整个过程还是比较曲折的&#xff0c;主要都是一些细小的问题&#xff0c;跑了很多遍模型才发现 参考轨迹生成问题不大&#xff0c;主要是参考横摆角和参考曲率部分有问题 atan和atan2 首先看下两者的区别 atan 函数&#xff1a;…

Electron[4] Electron最简单的打包实践

1 背景 前面三篇已经完成通过Electron搭建的最简单的HelloWorld应用了&#xff0c;虽然这个应用还没添加任何实质的功能&#xff0c;但是用来作为打包的案例&#xff0c;足矣。下面再分享下通过Electron-forge来将应用打包成安装包。 2 依赖 在Electron[2] Electron使用准备…

CF1898C Colorful Grid(构造)

题目链接 题目大意 n 行 m 列 的一个矩阵&#xff0c;每行有m - 1条边&#xff0c;每列有 n - 1 条边。 问一共走 k 条边&#xff0c;能不能从 &#xff08;1&#xff0c; 1&#xff09;&#xff0c;走到&#xff08;n&#xff0c; m&#xff09;&#xff0c;要求该路径上&am…

pandas数据处理闯关

pandas数据处理 第1关数据准备:将txt文件转成Excel文件第1关:将超市销售Excel文件根据商品的类别筛选存储任务描述相关知识:1. pd.read_excel()读取Excel文件2. DataFrame.to_excel() 向excel文件写入数据3. unique()唯一值函数4. 使用 loc 和 iloc 选择数据 本关代码 第2关数据…

基于YOLOv8深度学习的舰船目标分类检测系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

如何优雅地使用Mybatis逆向工程生成类

文/朱季谦 1.环境&#xff1a;SpringBoot 2.在pom.xml文件里引入相关依赖&#xff1a; 1 <plugin>2 <groupId>org.mybatis.generator</groupId>3 <artifactId>mybatis-generator-maven-plugin</artifactId>4 <version>1.3.6<…

【小沐学Python】Python实现WebUI网页图表(gradio)

文章目录 1、简介2、安装3、基本测试3.1 入门代码3.2 组件属性3.3 多个输入和输出组件3.4 图像示例3.5 聊天机器人3.6 模块&#xff1a;更灵活、更可控3.7 进度条 结语 1、简介 https://www.gradio.app/ Gradio是用友好的网络界面演示机器学习模型的最快方法&#xff0c;因此…

自然语言处理第2天:自然语言处理词语编码

​ ☁️主页 Nowl &#x1f525;专栏 《自然语言处理》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 ​​ 文章目录 一、自然语言处理介绍二、常见的词编码方式1.one-hot介绍缺点 2.词嵌入介绍说明 三、代码演示四、结语 一、自然语言处理介绍 自然语言处理&#xf…