DDD架构设计

news2024/12/5 2:30:14

今天的应用架构,意指软件系统中固定不变的代码结构、设计模式、规范和组件间的通信方式。在应用开发中架构之所以是最重要的第一步,因为一个好的架构能让系统安全、稳定、快速迭代。在一个团队内通过规定一个固定的架构设计,可以让团队内能力参差不齐的同学们都能有一个统一的开发规范,降低沟通成本,提升效率和代码质量。

  在做架构设计时,一个好的架构应该需要实现以下几个目标:

  • 独立于框架:架构不应该依赖某个外部的库或框架,不应该被框架的结构所束缚。
  • 独立于UI:前台展示的样式可能会随时发生变化(今天可能是网页、明天可能变成console、后天是独立app),但是底层架构不应该随之而变化。
  • 独立于底层数据源:无论今天你用MySQL、Oracle还是MongoDB、CouchDB,甚至使用文件系统,软件架构不应该因为不同的底层数据储存方式而产生巨大改变。
  • 独立于外部依赖:无论外部依赖如何变更、升级,业务的核心逻辑不应该随之而大幅变化。
  • 可测试:无论外部依赖了什么数据库、硬件、UI或者服务,业务的逻辑应该都能够快速被验证正确性。

这就好像是建筑中的楼宇,一个好的楼宇,无论内部承载了什么人、有什么样的活动、还是外部有什么风雨,一栋楼都应该屹立不倒,而且可以确保它不会倒。但是今天我们在做业务研发时,更多的会去关注一些宏观的架构,比如SOA架构、微服务架构,而忽略了应用内部的架构设计,很容易导致代码逻辑混乱,很难维护,容易产生bug而且很难发现。

1 . 传统Springboot MVC架构设计弊端

样例:

以微信支付收单为例,按照业务拆分,其业务需求可能会被如下拆分:

1. 从数据库获取数据,比如查看订单数据是否存在

2.业务参数校验

3.获取服务费,计算金额

4.微信下单

5.数据保存

6.发送消息

伪代码如下所示:

public class TransferServiceImpl implements TransferService {

    private OrderMapper orderDao;
    private WxForexService WxForex;

    @Override
    public Result<Boolean> transfer(String orderId, BigDecimal amount, String storeId) {
        // 1. 从数据库读取数据
        OrderDo targetOrderDo = orderDao.selectByOrderId(orderId);

        // 2. 业务参数校验
        if (amount != null) {
            throw new InvalidAmountException();
        }

        // 3. 获取服务费,计算金额
        BigDecimal exchangeRate = WxForex.getServiceRate(amount, storeId);
        BigDecimal newTarget = serviceFeeAmount.add(amount);
		// 4. 微信下单
		wxPay.orderPlacement(param);

        // 5. 更新到数据库
        orderDao.update(targetOrderDo);
        
        // 6. 发送消息
        mq.push(message);

        return Result.success(true);
    }

}

可以看到,一段业务代码里经常包含了参数校验、数据读取、业务计算、调用外部服务等多种逻辑。在这个案例里虽然是写在了同一个方法里,在真实代码中经常会被拆分成多个子方法,但实际效果是一样的,而在我们日常的工作中,绝大部分代码都或多或少的接近于此类结构。虽然这种类似于脚本的写法在功能上没有什么问题,但是长久来看,他有以下几个很大的问题:可维护性差、可扩展性差、可测试性差。

问题1-可维护性能差

一个应用最大的成本一般不是来自于开发阶段,而是应用整个生命周期的总维护成本,所以代码的可维护性代表了最终成本。

**可维护性 = 当依赖变化时,有多少代码需要随之改变**

参考以上的案例代码,上述脚本类的代码很难维护因为以下几点:

数据结构的不稳定性:OrderDO类是一个纯数据结构,映射了数据库中的一个表。这里的问题是数据库的表结构和设计是应用的外部依赖,长远来看都有可能会改变,比如数据库要做Sharding,或者换一个表设计,或者改变字段名。

依赖库的升级:AccountMapper依赖MyBatis的实现,如果MyBatis未来升级版本,可能会造成用法的不同(可以参考iBatis升级到基于注解的MyBatis的迁移成本)。同样的,如果未来换一个ORM体系,迁移成本也是巨大的。

计算逻辑的的不确定性:比如微信的服务费率计算未来很有可能会有变化,而且下单的服务费率也不仅仅是微信的,后面可能还需要计算支付宝的,甚至整个服务费率计算逻辑会推倒重来。

第三方服务API的接口变化:wxPay下单接口能保证不会变化吗?参数如果不是param怎么办??谁能保证未来接口不会改变?如果改变了,后续逻辑是不是y。

中间件更换:今天我们用Kafka发消息,明天如果要上阿里云用RocketMQ该怎么办?后天如果消息的序列化方式从String改为Binary该怎么办?

我们发现案例里的代码对于任何外部依赖的改变都会有比较大的影响。如果你的应用里有大量的此类代码,你每一天的时间基本上会被各种库升级、依赖服务升级、中间件升级、jar包冲突占满,最终这个应用变成了一个不敢升级、不敢部署、不敢写新功能、并且随时会爆发的炸弹,终有一天会给你带来惊喜。

问题2-可拓展性差

脚本式代码的第二大缺陷是:虽然写单个用例的代码非常高效简单,但是当用例多起来时,其扩展性会变得越来越差。

参考以上的代码,如果今天需要增加一个支付宝支付下单的能力,会发现基本上需要重新开发,基本上没有任何的可复用性。

在脚本式的架构下,一般做第一个需求都非常的快,但是做第N个需求时需要的时间很有可能是呈指数级上升的,绝大部分时间花费在老功能的重构和兼容上,最终你的创新速度会跌为0,促使老应用被推翻重构。

问题3-可测试性能差

除了部分工具类、框架类和中间件类的代码有比较高的测试覆盖之外,我们在日常工作中很难看到业务代码有比较好的测试覆盖,而绝大部分的上线前的测试属于人肉的“集成测试”。低测试率导致我们对代码质量很难有把控,容易错过边界条件,异常case只有线上爆发了才被动发现。而低测试覆盖率的主要原因是业务代码的可测试性比较差。

参考以上的一段代码,这种代码有极低的可测试性,代码的修改往往是牵一发而动全身。

2. 软件设计原则:

分析一下为什么以上的问题会出现?因为以上的代码违背了至少以下几个软件设计的原则:

  • 单一性原则(Single Responsibility Principle):单一性原则要求一个对象/类应该只有一个变更的原因。但是在这个案例里,代码可能会因为任意一个外部依赖或计算逻辑的改变而改变。
  • 依赖反转原则(Dependency Inversion Principle):依赖反转原则要求在代码中依赖抽象,而不是具体的实现。在这个案例里外部依赖都是具体的实现.
  • 开放封闭原则(Open Closed Principle):当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。在这个案例里的金额计算属于可能会被修改的代码,这个时候该逻辑应该需要被包装成为不可修改的计算类,新功能通过计算类的拓展实现。以上设计原则,几乎都需要通过接口实现,所以面向接口编程在软件设计中显得尤为重要,什么时候需要设计为接口,需依照以上设计原则。

我们需要对代码重构才能解决这些问题。

3. DDD概述

DDD (Domain Driven Design) 称为领域驱动设计,是一种架构设计思想,不同于设计模式,它是针对软件整体设计,用于实现“高内聚、低耦合”,而设计模式是针对某一特定场景实现的专属设计。DDD是服务于业务的,最好能达到不同开发或者业务经理 查看主流程代码,能够一眼看懂主要做了什么。

领域驱动设计(DDD)中采用的是松散分层架构,层间关系不那么严格。每层都可能使用它下面所有层的服务,而不仅仅是下一层的服务。每层都可能是半透明的,这意味着有些服务只对上一层可见,而有些服务对上面的所有层都可见。

分层的作用,从上往下:

  • 用户交互层:web 请求,rpc 请求,mq 消息等外部输入均被视为外部输入的请求,可能修改到内部的业务数据。
  • 业务应用层:与 MVC 中的 service 不同的不是,service 中存储着大量业务逻辑。但在应用服务的实现中,它负责编排、转发、校验等。
  • 领域层:或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手。
  • 基础设施层:主要有 2 方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现。

DDD常见概念:

实体: 包含属性以及行为操作的个体,比如Account实体,它包含了转账金额、币种、余额等属性以及转入、转出等操作。通过封装实体,可以将数据的任何操作都统一到实体类中实现内聚。同时实体和数据库字段无关,降低了数据库和实体的耦合

领域服务: DDD中代表特定服务,比如订单等。

防腐层:防止接口功能被污染,具体见下面解释

仓库: 和数据库关联,用于处理数据库数据。

工厂: 通过工厂模式设计加工具体的实现类

4. 使用DDD重构

 抽象数据存储层

第一步常见的操作是将Data Access层做抽象,降低系统对数据库的直接依赖。具体的方法如下:

新建OrderDo表示订单的实体,和数据库订单表映射,当数据库表字段改变时,同步修改OrderDo。同时创建

具体的简单代码实现如下:

public interface OrderInfoRepository {
    int createOrder(Order order);

    boolean existOrder(String orderId,String storeId);
}


@Repository("eastpayOrderInfoRepository")
public class EastpayOrderInfoRepository implements OrderInfoRepository{
    private static final Logger LOG = LoggerFactory.getLogger(EastpayOrderInfoRepository.class);

    @Resource
    private OrderMapper orderMapper;

    @Override
    public int createOrder(Order order) {
        int count =  orderMapper.insert(convertBean2OrderDo(order));
        LOG.info("Succeed to insert order info");
        return count;
    }

    @Override
    public boolean existOrder(String orderId,String storeId) {
        LambdaQueryWrapper<OrderDo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderDo::getOrderId, orderId);
        queryWrapper.eq(OrderDo::getStoreId, storeId);
        long count = orderMapper.selectCount(queryWrapper);
        // 如果查询到的记录数大于 0,表示订单存在
        return count > 0;
    }

  
    private OrderDo convertBean2OrderDo(Order order) {
        Date now = new Date();
        String dateTime = DateUtils.dateTime(now);
        ZoneId japanZoneId = ZoneId.of("Asia/Tokyo");
        ZonedDateTime japanTime = ZonedDateTime.now(japanZoneId);
//        BigDecimal exchangeRate = new BigDecimal(WechatpayqueryExchangeRateHandler.map.get("exchangeRate")).
//                divide(BigDecimal.TEN.pow(8));
        OrderDo orderDo = OrderDo.builder()
                .orgOrderId(order.getOrderId())
                .operationType(ApiOperation.THIRD_ORDER_PLACEMENT.code)
                .id(order.getId())
                .storeId(order.getStoreId())
                .orderId(order.getOrderId())

                .createDate(DateUtils.date(now))
                .trxnTime(dateTime)
                .oTrxnTime(japanTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))
                .channelId(order.getChannelId())
                .subChannelId(order.getSubChannelId())
                .trxnType(PaymentType.DB.name())
                .bgRetUrl(order.getBgRetUrl())
                .trxnCurrCode(order.getTrxnCurrCode())
                .trxnCurrAmt(order.getTrxnCurrAmt())
                .status(order.getOrderStatus())
                .errorCode(order.getGatewayErrCode())
                .errorMsg(order.getGatewayErrMsg())
                .payCurrCode(order.getPayCurrCode())
                .userId(order.getUserId())
                .serviceFee(order.getServiceFee())
                .productPrice(order.getProductPrice())
                .language("cn")
                .payType(order.getPayType())
//                .moMsgVersion(exchangeRate.toString())
                .orderDesc(order.getOrderDesc())
                .isSplitPay(String.valueOf(order.getIsSplitPay()))
                .serviceFeeRate(order.getServiceFeeRate())
                .emailStatus(ConstantsUtils.EMAIL_SENT)
                .origin(order.getOrigin())
                .build();
        return orderDo;
    }
}

Repository只负责Entity对象的存储和读取,而Repository的实现类完成数据库存储的细节。通过加入Repository接口,底层的数据库连接可以通过不同的实现类而替换。

通过Repository,改变业务代码的思维方式,让业务逻辑不再面向数据库编程,而是面向领域模型编程。

Repository作为一个接口类,可以比较容易的实现Mock或Stub,可以很容易测试。
EastpayOrderInfoRepository实现类,由于其职责被单一出来,只需要关注其到AccountDO的映射关系和Repository方法到DAO方法之间的映射关系,相对于来说更容易测试。

抽象外部服务

类似对于数据库的抽象,所有外部服务也需要通过抽象解决第三方服务不可控,入参出参强耦合的问题。在这个例子里我们抽象出IServiceFeeInfoService的服务,和一个ServiceFeeInfoService实现类(自己读取数据库实现的,实际通过调用第三方实现):

public interface IServiceFeeInfoService {
    Map<String, BigDecimal> obtainServiceFeeInfo(Order order);
}



@Service
public class ServiceFeeInfoService implements IServiceFeeInfoService{
    @Resource(name = "datasourceServiceFeeRateRepository")
    private ServiceFeeRateRepository serviceFeeRateRepository;

    @Override
    public Map<String, BigDecimal> obtainServiceFeeInfo(Order order) {
        BigDecimal amount = order.getProductPrice();
        String merchantId = order.getStoreId();
        String payType = order.getPayType();
        String channel = order.getChannelId();
        MdiscxDo mdiscxDo = serviceFeeRateRepository.obtainServiceFeeRate(amount,merchantId,payType,channel);
        BigDecimal serviceFeeRate = BigDecimal.ZERO;

        BigDecimal amountRegion1 =  Optional.ofNullable(mdiscxDo.getMdTierAmt1()).map(s -> new BigDecimal(s.toString())).orElse(null);
        BigDecimal amountRegion2 =  Optional.ofNullable(mdiscxDo.getMdTierAmt2()).map(s -> new BigDecimal(s.toString())).orElse(null);
        BigDecimal amountRegion3 =  Optional.ofNullable(mdiscxDo.getMdTierAmt3()).map(s -> new BigDecimal(s.toString())).orElse(null);

        BigDecimal serviceFeeRate1 = Optional.ofNullable(mdiscxDo.getMdPercentage1()).map(s -> new BigDecimal(s.toString())).orElse(BigDecimal.ZERO);
        BigDecimal serviceFeeRate2 = Optional.ofNullable(mdiscxDo.getMdPercentage2()).map(s -> new BigDecimal(s.toString())).orElse(BigDecimal.ZERO);
        BigDecimal serviceFeeRate3 = Optional.ofNullable(mdiscxDo.getMdPercentage3()).map(s -> new BigDecimal(s.toString())).orElse(BigDecimal.ZERO);
        BigDecimal serviceFeeLimitAmt = Optional.ofNullable(mdiscxDo.getMdMaxAmt()).map(s -> new BigDecimal(s.toString())).orElse(null);
        if (amountRegion3 != null && amount.compareTo(amountRegion3) >= 0) {
            serviceFeeRate = serviceFeeRate3;
        } else if (amountRegion2 != null && amount.compareTo(amountRegion2) >= 0) {
            serviceFeeRate = serviceFeeRate2;
        } else if (amountRegion1 != null && amount.compareTo(amountRegion1) >= 0) {
            serviceFeeRate = serviceFeeRate1;
        }

        String formatFee = ApiUtils.formatAmount(amount.multiply(serviceFeeRate, MathContext.DECIMAL32), order.getTrxnCurrCode());
        BigDecimal serviceFee = serviceFeeLimitAmt.compareTo(new BigDecimal(formatFee)) <= 0 ? serviceFeeLimitAmt : new BigDecimal(formatFee);
        BigDecimal totalAmount = amount.add(serviceFee);

        Map<String,BigDecimal> res = new HashMap<>();
        res.put(ApiFields.SERVICE_FEE_RATE,serviceFeeRate);
        res.put(ApiFields.SERVICE_FEE_LIMIT_AMT,serviceFeeLimitAmt);
        res.put(ApiFields.SERVICE_FEE,serviceFee);
        res.put(ApiFields.TRANSACTION_AMOUNT,totalAmount);
        return res;
    }
}

 防腐层(ACL)

这种常见的设计模式叫做Anti-Corruption Layer(防腐层或ACL)。很多时候我们的系统会去依赖其他的系统,而被依赖的系统可能包含不合理的数据结构、API、协议或技术实现,如果对外部系统强依赖,会导致我们的系统被”腐蚀“。这个时候,通过在系统间加入一个防腐层,能够有效的隔离外部依赖和内部逻辑,无论外部如何变更,内部代码可以尽可能的保持不变。

ACL 不仅仅只是多了一层调用,在实际开发中ACL能够提供更多强大的功能:

适配器:很多时候外部依赖的数据、接口和协议并不符合内部规范,通过适配器模式,可以将数据转化逻辑封装到ACL内部,降低对业务代码的侵入。

兜底:如果外部依赖的稳定性较差,一个能够有效提升我们系统稳定性的策略是通过ACL起到兜底的作用,比如当外部依赖出问题后,返回最近一次成功的缓存或业务兜底数据。这种兜底逻辑一般都比较复杂,如果散落在核心业务代码中会很难维护,通过集中在ACL中,更加容易被测试和修改。

易于测试:类似于之前的Repository,ACL的接口类能够很容易的实现Mock或Stub,以便于单元测试。

抽象中间件

类似于抽象外部服务,抽象中间件的做法也大致相同,在此不做赘述。

接口参数隔离

在很多时候,程序员开发的下单接口并不由自己决定,尤其是参数规则,需要根据合作方的需求来调整,往往是A合作方要求接口需要使用AParam接受,B合作方要求接口使用BParam接受,这时如果没有对下单接口的参数进行隔离,就会发现接口难以维护了。

比如,使用下单接口,我们使用的是OrderPlacementDto接受外部的请求参数,并且将此param传递到后续所有业务逻辑,包括参数校验、服务费率获取、金额计算、数据保存等等。这时当需要对OrderPlacementDto进行修改,你会发现项目处处都是修改点,不利于维护和测试。

针对此场景,我们可以新建系统自己的业务实体类Order,当接收到下单请求时,将参数先转换为Order,这样不管合作方要求我们如何修改接口入参,我们只需要修改由OrderPlacementDto --> Order的转换逻辑就行。

5. 总结

  DDD 强调技术主动理解业务,业务如何实现,程序就如何构建。 DDD 强调在对职责进行清晰地划分 后,让技术只要“ 刚刚好 的解决业务问题即可。至于以后的需求变更,可以从领域划分的角度,拆分落 实到不同的领域,然后交由各个领域分工合作。此时各种各样的设计模式、工具、框架等,就不再是解决业务的核心,而是随手拈来的工具。
  这里由于问题域是未知的,所以肯定也没有办法说一下就能提供一个完美的解决方案。但是,有六个字你一定很熟悉。高内聚,低耦合 。让整个系统内的各个业务模块功能尽量内聚,业务模块与业务模块 之间减少相互依赖。这样即使面对更复杂的问题,也可以通过将问题拆解成为多个业务模块之间的协作。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

再来聊聊总线机制

背景 之前写过一篇《KafkaPostgreSql&#xff0c;构建一个总线服务》&#xff0c;近期在实践过程中又踩了一些坑&#xff0c;有了一些新的体验&#xff0c;拿出来再说道说道。 我们说EventBus 是一种设计模式和编程工具&#xff0c;它简化了应用程序组件之间的通信。通过使用…

怎么做DNS污染检测

DNS污染是指通过恶意手段篡改DNS解析结果&#xff0c;导致用户访问错误或恶意网站的行为。这种行为不仅影响用户体验&#xff0c;还可能带来安全风险。以下是几种检测DNS污染的方法&#xff1a; 1. 使用在线DNS检查工具 可以使用在线工具如帝恩思旗下的拨测在线DNS检测工具等…

视频融合×室内定位×数字孪生

随着物联网技术的迅猛发展&#xff0c;室内定位与视频融合技术在各行各业中得到了广泛应用。不仅能够提供精确的位置信息&#xff0c;还能通过实时视频监控实现全方位数据的可视化。 与此同时&#xff0c;数字孪生等技术的兴起为智慧城市、智慧工厂等应用提供了强大支持&#…

合规性要求对漏洞管理策略的影响

讨论漏洞管理中持续面临的挑战&#xff0c;包括确定漏洞的优先级和解决修补延迟问题。 介绍合规性要求以及自动化如何简化漏洞管理流程。 您认为为什么尽管技术不断进步&#xff0c;但优先考虑漏洞和修补延迟等挑战仍然存在&#xff1f; 企业基础设施日益复杂&#xff0c;攻…

基于Java Springboot诗词学习APP且微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse微信开…

常见问题QA的前端代码

这个的后端代码参见此文 使用语言向量建立常见问题的模糊搜索-CSDN博客https://blog.csdn.net/chenchihwen/article/details/144207262?spm1001.2014.3001.5501 这段代码实现了一个简单的问答页面&#xff0c;页面分为左右两部分&#xff0c;左侧用于展示对话记录&#xff0c…

CSS学习记录03

CSS背景 CSS 背景属性用于定义元素的背景效果。 CSS background-color background-color属性指定元素的背景色。 页面的背景色设置如下&#xff1a; body {background-color: lightblue; } 通过CSS&#xff0c;颜色通常由以下方式指定&#xff1a; 有效的颜色名称-比如“…

【k8s】kubelet 的相关证书

在 Kubernetes 集群中&#xff0c;kubelet 使用的证书通常存放在节点上的特定目录。这些证书用于 kubelet 与 API 服务器之间的安全通信。具体的位置可能会根据你的 Kubernetes 安装方式和配置有所不同&#xff0c;下图是我自己环境【通过 kubeadm 安装的集群】中的kubelet的证…

JavaWeb:Servlet (学习笔记)【1】

目录 一&#xff0c;Servlet介绍 1&#xff0c;简介 2&#xff0c;Servlet技术特点 3&#xff0c;Servlet在应用程序中的位置 4&#xff0c;Servlet在程序中到底处于一个什么地位? 二&#xff0c;servlet运行过程&#xff1a; 三&#xff0c;servlet路径配置 四&#x…

STM32-C语言基础知识

C语言基础知识 stdint.h简介 给寄存器某个位赋值 给位6赋值为1流程&#xff1a;先清0&#xff0c;再赋值 带参数的宏定义 建议使用do {…}while(0)来构造宏定义 条件编译 条件编译后面必须跟宏语句&#xff0c;如#if _LED_H 指针使用常见的2大问题 1、未初始化 2、越界使…

Android 应用单元测试涉及 Telephony 环境初始化问题

Telephony 相关类注入问题 SubscriptionManager Cannot invoke "android.telephony.SubscriptionManager.getActiveSubscriptionInfoList()" because "this.mSubscriptionManager" is nulljava.lang.NullPointerException: Cannot invoke "android.t…

mysql 存储结构的进化之路

文章目录 前言一、线性结构二、二叉树&#xff08;BST&#xff09;三、平衡二叉树&#xff08;AVL&#xff09;四、多路平衡查找树&#xff08;B Tree&#xff09;五、加强版多路平衡查找树&#xff08;B Tree&#xff09;总结 前言 树形结构是一种具有层次关系的数据结构&…

高速定向广播声光预警系统赋能高速安全管控

近年来&#xff0c;高速重大交通事故屡见不鲜&#xff0c;安全管控一直是高速运营的重中之重。如何利用现代化技术和信息化手段&#xff0c;创新、智能、高效的压降交通事故的发生概率&#xff0c;优化交通安全管控质量&#xff0c;是近年来交管部门的主要工作&#xff0c;也是…

【机器学习】CatBoost 模型实践:回归与分类的全流程解析

一. 引言 本篇博客首发于掘金 https://juejin.cn/post/7441027173430018067。 PS&#xff1a;转载自己的文章也算原创吧。 在机器学习领域&#xff0c;CatBoost 是一款强大的梯度提升框架&#xff0c;特别适合处理带有类别特征的数据。本篇博客以脱敏后的保险数据集为例&#x…

游戏引擎学习第25天

Git: https://gitee.com/mrxiao_com/2d_game 今天的计划 总结和复述&#xff1a; 这段时间的工作已经接近尾声&#xff0c;虽然每次编程的时间只有一个小时&#xff0c;但每一天的进展都带来不少收获。尽管看起来似乎花费了很多时间&#xff0c;实际上这些日积月累的时间并未…

AI开发:生成式对抗网络入门 模型训练和图像生成 -Python 机器学习

阶段1&#xff1a;GAN是个啥&#xff1f; 生成式对抗网络&#xff08;Generative Adversarial Networks, GAN&#xff09;&#xff0c;名字听着就有点“对抗”的意思&#xff0c;没错&#xff01;它其实是两个神经网络互相斗智斗勇的游戏&#xff1a; 生成器&#xff08;Gene…

040集——CAD中放烟花(CAD—C#二次开发入门)

效果如下&#xff1a; 单一颜色的烟花&#xff1a; 渐变色的火花&#xff1a; namespace AcTools {public class HH{public static TransientManager tm TransientManager.CurrentTransientManager;public static Random rand new Random();public static Vector3D G new V…

JavaScript实现tab栏切换

JavaScript实现tab栏切换 代码功能概述 这段代码实现了一个简单的选项卡&#xff08;Tab&#xff09;切换功能。它通过操作 HTML 元素的类名&#xff08;class&#xff09;来控制哪些选项卡&#xff08;Tab&#xff09;和对应的内容板块显示&#xff0c;哪些隐藏。基本思路是先…

【天地图】HTML页面实现车辆轨迹、起始点标记和轨迹打点的完整功能

目录 一、功能演示 二、完整代码 三、参考文档 一、功能演示 运行以后完整的效果如下&#xff1a; 点击开始&#xff0c;小车会沿着轨迹进行移动&#xff0c;点击轨迹点会显示经纬度和时间&#xff1a; 二、完整代码 废话不多说&#xff0c;直接给完整代码&#xff0c;替换…

HCIA笔记6--路由基础与静态路由:浮动路由、缺省路由、迭代查找

文章目录 0. 概念1.路由器工作原理2. 跨网访问流程3. 静态路由配置4. 静态路由的应用场景4.1 路由备份4.2 浮动路由4.3 缺省路由 5. 迭代路由6 问题6.1 为什么路由表中有的下一跳的地址有接口&#xff1f;6.2 个人电脑的网关本质是什么&#xff1f; 0. 概念 自治系统&#xff…