CPP-Summit-2020 学习:多范式融合的Modern C++ 软件设计(中)

1⃣ 面向对象建模(Object-Oriented Modeling, OOM)

概念理解:

  • 面向对象建模是用 对象和类的概念描述系统的结构和行为。
  • 核心思想:把系统抽象成 对象(Object),对象具有 属性(属性/状态)方法(行为)
  • 目标:形成可视化模型,为分析、设计、实现提供蓝图。
    核心工具:
  • UML 类图(Class Diagram)
  • UML 用例图(Use Case Diagram)
  • UML 状态图(State Diagram)
  • UML 活动图(Activity Diagram)
    示例 UML 类图(Mermaid)

places

10..*

User

+id: int

+name: string

+login()

+logout()

Order

+orderId: int

+amount: float

+status: string

+pay()

+ship()

解释:

  • 用户(User)和订单(Order)是对象
  • 用户可以下多个订单,表现关系为 “一对多”
  • 类图是面向对象建模的核心工具
    明白了,我帮你把原文里 Python 部分直接改成 C++ 实现,保持原有描述、概念和结构不变,只改代码。

2⃣ 需求分析建模(Requirements Modeling)

概念理解:

  • 需求分析建模关注 系统应该做什么,而非如何实现。
  • 方法:用 用例图、活动图 表达系统功能、用户行为及流程。
  • 目标:明确系统需求,为设计提供输入。
    示例(Mermaid 用例图):

下单

支付

查看订单

管理商品

User

Place Order

Pay

View Order

Admin

Manage Products

解释:

  • “参与者(Actor)” = 系统外部实体(User, Admin)
  • “用例(Use Case)” = 系统提供的功能
  • 需求分析阶段不关注类、方法、算法

3⃣ 面向对象分析(Object-Oriented Analysis, OOA)

概念理解:

  • 面向对象分析关注 现实世界问题的对象抽象,找出系统的对象及关系。
  • 核心任务:
    1. 识别对象与类
    2. 分析对象属性和行为
    3. 识别对象之间关系(关联、继承、聚合、组合)
      示例分析:
  • 对象:User, Order, Product
  • 属性:User.id, Order.amount
  • 行为:Order.pay(), Product.updateStock()
  • 关系:User 下 Order, Order 包含 Product
    公式表达(业务量计算示意):
    T o t a l R e v e n u e = ∑ i = 1 N O r d e r i . a m o u n t TotalRevenue = \sum_{i=1}^{N} Order_i.amount TotalRevenue=i=1∑N​Orderi​.amount
  • N N N = 系统订单总数
  • O r d e r i . a m o u n t Order_i.amount Orderi​.amount = 第 i 个订单金额

4⃣ 面向对象设计(Object-Oriented Design, OOD)

概念理解:

  • 面向对象设计是 OOA结果到可实现系统的映射,即把分析得到的对象建模成具体类结构和接口。
  • 核心任务:
    1. 定义类接口和方法
    2. 设计继承结构(父类、子类)
    3. 定义设计模式(如单例、观察者、工厂等)
      示例设计(C++ 类设计):
#include<iostream>#include<vector>#include<string>usingnamespace std;classOrder;// 前向声明classUser{private:int id; string name; vector<Order*> orders;public:User(int user_id,const string& user_name):id(user_id),name(user_name){}voidplaceOrder(Order* order){ orders.push_back(order);}};classOrder{private:int orderId;double amount; string status;public:Order(int id,double amt):orderId(id),amount(amt),status("Created"){}voidpay(){ status ="Paid";}voidship(){if(status=="Paid") status ="Shipped";}};

✓ 设计阶段关注 接口、方法、类之间关系,不涉及具体算法实现优化

5⃣ 面向对象编码(Object-Oriented Programming, OOP)

概念理解:

  • 编码阶段将设计转化为 可执行代码,实现类、对象、继承、多态、封装等特性。
  • 核心思想:
    • 封装(Encapsulation):隐藏内部实现
    • 继承(Inheritance):代码复用
    • 多态(Polymorphism):统一接口调用不同对象
    • 组合(Composition):对象间复合关系
      示例 C++ 代码:
#include<iostream>#include<vector>#include<string>usingnamespace std;classOrder;classUser{private:int id; string name; vector<Order*> orders;public:User(int user_id,const string& user_name):id(user_id),name(user_name){}voidplaceOrder(Order* order); string getName()const{return name;}};classOrder{private:int orderId;double amount; string status;public:Order(int id,double amt):orderId(id),amount(amt),status("Created"){}voidpay(){ status ="Paid"; cout <<"订单 "<< orderId <<" 已支付\n";}voidship(){if(status=="Paid") cout <<"订单 "<< orderId <<" 已发货\n";else cout <<"订单 "<< orderId <<" 尚未支付,无法发货\n";}intgetId()const{return orderId;}};voidUser::placeOrder(Order* order){ orders.push_back(order); cout <<"用户 "<< name <<" 下单 "<< order->getId()<<"\n";}// 测试intmain(){ User u(1,"Alice"); Order o1(1001,250.0); u.placeOrder(&o1); o1.pay(); o1.ship();return0;}

输出:

用户 Alice 下单 1001 订单 1001 已支付 订单 1001 已发货 

编码阶段重点:

  • 类和对象可直接执行
  • 设计模式可直接实现
  • 可以加入异常处理、日志等工程实践

✓ 总结流程对比


阶段关注点工具/表示关键成果
面向对象建模 OOM系统对象抽象UML 类图、状态图系统蓝图
需求分析建模系统功能UML 用例图、活动图功能需求文档
面向对象分析 OOA现实对象抽象类、对象关系图分析模型
面向对象设计 OOD类设计、接口设计类设计图、设计模式设计模型
面向对象编码 OOP可执行实现Python/Java/C++可运行程序

领域驱动设计(Domain-Driven Design, DDD)SVG 图

领域子域...N:1核心域通用域支撑域限界上下文或子子域(应用层)N:1应用服务限界上下文协同简单逻辑流程、服务组合编排仓储通用语言第二层物理边界聚合(领域层)N:1领域服务 领域事件聚合根核心业务逻辑业务内聚仓储 事务控制第一层逻辑边界聚合协同 事务控制实体N:1实体类唯一标识充血模型对象行为值对象N:1无标识值 属性集单一属性访问顺序

1⃣ 领域驱动设计概念理解

领域驱动设计核心思想:

  • 聚焦 业务核心领域,而非技术实现
  • 将系统划分为不同 子域(Subdomain):核心域、通用域、支撑域
  • 在每个域中,使用 限界上下文(Bounded Context) 明确模型边界
  • 设计 聚合(Aggregate)、实体(Entity)、值对象(Value Object)、领域服务(Domain Service) 来组织业务逻辑
    图中元素解释:
  1. 领域(Domain)
    • 整个系统的业务世界
    • 外层矩形表示系统整体领域
  2. 子域(Subdomain)
    • 核心域(Core Domain):业务价值最高的部分
    • 通用域(Generic Domain):通用逻辑,可复用
    • 支撑域(Supporting Domain):辅助业务功能
  3. 限界上下文(Bounded Context)
    • 子域内部的具体模型边界
    • 负责一组完整功能,内部一致性由上下文保证
  4. 聚合(Aggregate)
    • 聚合根(Aggregate Root)控制一组相关对象
    • 确保事务边界与一致性
    • 内部可以有实体、值对象和领域服务
  5. 实体(Entity)
    • 拥有唯一标识 (ID)
    • 包含业务属性和行为
    • 例如订单、用户、产品
  6. 值对象(Value Object)
    • 无唯一标识
    • 用于封装属性集合
    • 例如地址、金额、时间范围
  7. 领域服务(Domain Service)
    • 处理不属于某个实体的业务逻辑
    • 例如下单流程、支付逻辑
  8. 仓储(Repository)
    • 提供实体持久化访问接口
    • 聚合根通过仓储进行存取

2⃣ SVG 图解对应业务理解

箭头表示访问顺序和聚合依赖关系

  • 红色虚线箭头标注访问顺序
  • 核心域内部通过聚合根访问实体和值对象
  • 事务控制和聚合协同保证一致性
    数学表示聚合逻辑:
    假设一个订单聚合包含多个商品,订单总金额可表示为:
    O r d e r T o t a l = ∑ i = 1 N P r o d u c t i . P r i c e × P r o d u c t i . Q u a n t i t y OrderTotal = \sum_{i=1}^{N} Product_i.Price \times Product_i.Quantity OrderTotal=i=1∑N​Producti​.Price×Producti​.Quantity
  • N N N = 聚合中商品数量
  • P r o d u c t i . P r i c e Product_i.Price Producti​.Price = 第 i i i 个商品单价
  • P r o d u c t i . Q u a n t i t y Product_i.Quantity Producti​.Quantity = 第 i i i 个商品数量
    若聚合中涉及折扣策略 D i s c o u n t R a t e DiscountRate DiscountRate,最终金额为:
    O r d e r F i n a l = O r d e r T o t a l × ( 1 − D i s c o u n t R a t e ) OrderFinal = OrderTotal \times (1 - DiscountRate) OrderFinal=OrderTotal×(1−DiscountRate)

3⃣ C++ 领域驱动设计示例

下面给出 聚合根 + 实体 + 值对象 + 领域服务 示例,体现 DDD 思想。

#include<iostream>#include<vector>#include<string>usingnamespace std;// ====================// 值对象(Value Object)// ====================classMoney{private:double amount; string currency;public:Money(double amt,const string& cur):amount(amt),currency(cur){}doublegetAmount()const{return amount;} string getCurrency()const{return currency;}};// ====================// 实体(Entity)// ====================classProduct{private:int id;// 唯一标识 string name; Money price;// 值对象public:Product(int pid,const string& pname, Money p):id(pid),name(pname),price(p){}intgetId()const{return id;} Money getPrice()const{return price;}};// ====================// 聚合根(Aggregate Root)// ====================classOrder{private:int orderId; vector<Product> products;public:Order(int id):orderId(id){}voidaddProduct(const Product& p){ products.push_back(p);}// 聚合内部计算总金额doubletotalAmount()const{double total =0;for(constauto& p : products){ total += p.getPrice().getAmount();}return total;}voidprintSummary()const{ cout <<"订单 "<< orderId <<" 总金额: "<<totalAmount()<< endl;}};// ====================// 领域服务(Domain Service)// ====================classOrderService{public:staticvoidcheckout(Order& order){ cout <<"执行下单流程..."<< endl;// 可以添加库存检查、支付、仓储保存等逻辑 order.printSummary(); cout <<"订单完成!"<< endl;}};// ====================// 测试示例// ====================intmain(){ Product p1(101,"鼠标",Money(100,"CNY")); Product p2(102,"键盘",Money(200,"CNY")); Order order(1001); order.addProduct(p1); order.addProduct(p2);OrderService::checkout(order);return0;}

输出示例

执行下单流程... 订单 1001 总金额: 300 订单完成! 

4⃣ 总结

  1. 聚合根(Aggregate Root):控制聚合内对象一致性,外部通过它访问聚合
  2. 实体(Entity):有唯一标识,负责自身行为
  3. 值对象(Value Object):无唯一标识,只封装属性
  4. 领域服务(Domain Service):处理不属于单个实体的业务逻辑
  5. 仓储(Repository):提供聚合的持久化接口
    ✓ DDD 核心:业务优先、模型驱动、限界上下文、聚合一致性

面向模型(Model-Driven / Domain-Driven)C++ 实现模式

1⃣ 表达概念(Concept Representation)

概念理解:

  • 面向模型实现模式的核心是 将领域模型转化为代码模型
  • 每个业务概念在系统中对应一个 类或对象
  • 使用 类、成员变量、方法 表示属性和行为。
    示例:订单聚合中的实体和值对象
#include<string>#include<vector>#include<iostream>usingnamespace std;// ========================// 值对象(Value Object)// ========================classMoney{private:double amount;// 金额 string currency;// 币种public:Money(double amt,const string& cur):amount(amt),currency(cur){}doublegetAmount()const{return amount;} string getCurrency()const{return currency;}};// ========================// 实体(Entity)// ========================classProduct{private:int id;// 唯一标识 string name; Money price;// 值对象public:Product(int pid,const string& pname, Money p):id(pid),name(pname),price(p){}intgetId()const{return id;} Money getPrice()const{return price;}};

解释

  • 实体(Entity):有唯一标识(ID),行为和属性
  • 值对象(Value Object):无唯一标识,封装属性集合

2⃣ 表达关系(Relationship Representation)

概念理解:

  • 对象之间关系通过 成员引用、指针或集合 表示。
  • 常见关系:
    • 关联(Association):对象间一般关系
    • 组合(Composition):生命周期绑定
    • 聚合(Aggregation):聚合根管理子对象
      示例:订单聚合根包含多个产品
// 聚合根(Aggregate Root)classOrder{private:int orderId; vector<Product> products;// 聚合关系public:Order(int id):orderId(id){}voidaddProduct(const Product& p){ products.push_back(p);}doubletotalAmount()const{double total =0;for(constauto& p : products) total += p.getPrice().getAmount();return total;}voidprintSummary()const{ cout <<"订单 "<< orderId <<" 总金额: "<<totalAmount()<< endl;}};

公式表达
O r d e r T o t a l = ∑ i = 1 N P r o d u c t i . P r i c e OrderTotal = \sum_{i=1}^{N} Product_i.Price OrderTotal=i=1∑N​Producti​.Price

  • N N N = 聚合中产品数量
  • P r o d u c t i . P r i c e Product_i.Price Producti​.Price = 第 i i i 个产品价格

3⃣ 生命周期管理(Lifecycle Management)

概念理解:

  • 面向模型实现要管理对象创建、更新、删除的生命周期。
  • C++ 提供:
    • 构造函数/析构函数:管理对象初始化与销毁
    • 智能指针(shared_ptr/unique_ptr):管理内存和引用计数
    • RAII(Resource Acquisition Is Initialization):资源管理
      示例:使用智能指针管理聚合
#include<memory>intmain(){auto order =make_shared<Order>(1001);// RAII 自动管理生命周期auto p1 =Product(101,"鼠标",Money(100,"CNY"));auto p2 =Product(102,"键盘",Money(200,"CNY")); order->addProduct(p1); order->addProduct(p2); order->printSummary();// 自动释放 order 对象及内部 vectorreturn0;}

解释

  • make_shared 创建对象并自动管理内存
  • 聚合内部对象生命周期随聚合根管理
  • RAII 模式保证异常安全

4⃣ 物理设计(Physical Design)

概念理解:

  • 面向模型实现不仅在内存中建模,还要考虑 持久化、分布式和性能
  • 常见模式:
    • 仓储模式(Repository):抽象数据访问
    • 工厂模式(Factory):统一对象创建
    • 服务(Service):实现业务逻辑
      示例:仓储模式访问订单聚合
classOrderRepository{public:voidsave(const Order& order){// 这里可以实现数据库持久化逻辑 cout <<"保存订单 "<< order.totalAmount()<<" 到数据库"<< endl;} Order findById(int orderId){// 查询逻辑(示例返回空对象)returnOrder(orderId);}};// 使用示例intmain(){ Order order(1002); OrderRepository repo; repo.save(order);}

公式表示数据聚合或查询计算
T o t a l R e v e n u e = ∑ i = 1 M O r d e r i . T o t a l A m o u n t TotalRevenue = \sum_{i=1}^{M} Order_i.TotalAmount TotalRevenue=i=1∑M​Orderi​.TotalAmount

  • M M M = 数据库中所有订单数
  • O r d e r i . T o t a l A m o u n t Order_i.TotalAmount Orderi​.TotalAmount = 第 i i i 个订单总金额

5⃣ 总结(面向模型 C++ 实现模式)


模块实现思路C++ 技术/模式
表达概念用类、成员变量和方法表示领域对象Entity、Value Object
表达关系用指针/引用/容器表示关联、组合和聚合vector、shared_ptr、组合/聚合关系
生命周期管理管理对象创建、销毁和异常安全构造/析构函数、智能指针、RAII
物理设计设计数据持久化、聚合访问、业务服务Repository、Factory、Service 模式
聚合计算公式对业务数据计算抽象 O r d e r T o t a l = ∑ i = 1 N P r o d u c t i . P r i c e OrderTotal = \sum_{i=1}^{N} Product_i.Price OrderTotal=i=1NProducti.Price

SOLID 原则及隐藏实现细节的 SVG 图

1⃣ SOLID 原则概念理解

SOLID 是面向对象设计的五大原则,核心是 通过接口与多态设计系统,使系统对变化扩展而非修改

原则解释核心思想
S 单一职责原则(SRP)每个类/模块只负责一个职责,职责变化是修改的原因通过接口隔离变和不变,降低耦合
O 开放封闭原则(OCP)对扩展开放,对修改封闭使用多态接口允许新功能扩展,而不修改已有类
L 里氏替换原则(LSP)子类必须可以替换父类且不破坏系统接口设计要隐藏细节,实现细节对外透明
I 接口隔离原则(ISP)面向不同客户的接口要分离不强迫客户端依赖它不需要的方法
D 依赖倒置原则(DIP)高层模块不依赖低层模块,二者依赖抽象接口由使用方控制,实现细节可变化

✓ 核心思想:对客户代码隐藏细节,使用接口和多态隔离变化

2⃣ SVG 图理解

客户代码API对客户代码隐藏细节正交正交正交

图中结构表达了 接口隔离和细节隐藏

  1. 客户代码(Client Code)
    • 使用接口而非具体实现
    • 不依赖实现细节
  2. API / 接口
    • 对外暴露给客户
    • 隔离实现变化
    • 图中红色箭头表示 对客户隐藏细节
  3. 内部实现(Internal Implementation)
    • 多个模块用颜色矩形表示
    • 通过接口与客户正交(独立)
    • 内部变化不会影响客户代码
  4. 正交性
    • 图中标注“正交”,表示模块之间解耦
    • 高内聚、低耦合

3⃣ C++ 示例:实现 SOLID 原则

假设我们做 支付模块,演示 接口、单一职责、多态

#include<iostream>#include<memory>usingnamespace std;// ===========================// 接口隔离原则 & 依赖倒置原则// ===========================classIPayment{// DIP: 高层依赖抽象接口public:virtualvoidpay(double amount)=0;virtual~IPayment()=default;};// ===========================// 单一职责原则:不同支付方式单独实现// ===========================classAlipay:publicIPayment{public:voidpay(double amount)override{// 实现细节对外隐藏 cout <<"使用支付宝支付 "<< amount <<" 元"<< endl;}};classWechatPay:publicIPayment{public:voidpay(double amount)override{ cout <<"使用微信支付 "<< amount <<" 元"<< endl;}};// ===========================// 客户代码只依赖接口// ===========================classOrder{private:double amount; shared_ptr<IPayment> paymentMethod;// OCP: 支持扩展不同支付方式public:Order(double amt, shared_ptr<IPayment> payMethod):amount(amt),paymentMethod(payMethod){}voidcheckout(){ paymentMethod->pay(amount);// 调用接口,多态实现隐藏细节}};// ===========================// 测试// ===========================intmain(){ shared_ptr<IPayment> alipay =make_shared<Alipay>(); shared_ptr<IPayment> wechat =make_shared<WechatPay>(); Order order1(100, alipay); Order order2(200, wechat); order1.checkout();// 输出: 使用支付宝支付 100 元 order2.checkout();// 输出: 使用微信支付 200 元return0;}

4⃣ 数学公式表达多态与扩展

  1. 系统总支付金额可以抽象为:
    T o t a l P a y m e n t = ∑ i = 1 N P a y m e n t i ( a m o u n t ) TotalPayment = \sum_{i=1}^{N} Payment_i(amount) TotalPayment=i=1∑N​Paymenti​(amount)
  • P a y m e n t i Payment_i Paymenti​ = 不同支付方式实现的 pay() 方法
  • N N N = 支付对象数量
  1. 扩展新支付方式时,不修改客户代码,只需:
    P a y m e n t n e w ∈ I P a y m e n t Payment_{new} \in IPayment Paymentnew​∈IPayment
    系统自动支持多态调用:
    O r d e r . c h e c k o u t ( ) = P a y m e n t i . p a y ( a m o u n t ) Order.checkout() = Payment_i.pay(amount) Order.checkout()=Paymenti​.pay(amount)
    ✓ 符合 开放封闭原则 + 里氏替换原则

5⃣ 总结

  1. 对客户隐藏实现细节
    • 客户依赖接口而非实现
    • 内部实现可随意变化
  2. 单一职责 + 接口隔离
    • 每个类只做一件事
    • 每个接口只提供客户需要的方法
  3. 依赖倒置 + 多态
    • 高层模块依赖抽象接口
    • 子类替换父类不破坏系统
  4. 公式化抽象
    • 支付总额、订单处理、扩展方式都可公式化表示

完整的 C++ 多态实现示例

1⃣ C++ 多态概念

多态(Polymorphism)允许相同接口调用不同实现,C++ 支持两类多态:

类型说明C++实现方式
静态多态编译期决定调用函数重载、模板编程、std::variant + std::visit
动态多态运行时决定调用虚函数、函数指针、类型擦除

✓ 核心目标:统一接口,解耦调用与实现,支持系统扩展而不修改已有代码

2⃣ 静态多态示例(类型擦除 + variant)

#include<iostream>#include<vector>#include<variant>// ============================// 图形类型// ============================structTriangle{voiddraw()const{ std::cout <<"△"<< std::endl;// 绘制三角形}};structCircle{voiddraw()const{ std::cout <<"○"<< std::endl;// 绘制圆形}};// ============================// 单元测试框架 Google Test 示例// ============================#include<gtest/gtest.h>using Shape = std::variant<Triangle, Circle>;// 类型擦除,实现静态多态classDrawShapeTest:public::testing::Test{protected: std::vector<Shape> shapes;voidSetUp()override{ shapes ={Circle{}, Triangle{}};// 初始化图形集合}};TEST_F(DrawShapeTest, DrawAllShapes){// 遍历集合,调用 draw() 方法for(constauto&shape : shapes){ std::visit([](constauto&s){ s.draw();}, shape);// 静态多态分派}// 数学表示:// 对每个形状 Shape_i:// Draw(Shape_i) = // Triangle.draw() if Shape_i = Triangle// Circle.draw() if Shape_i = Circle// 总绘制次数:// TotalDraw = Σ_{i=1}^{N} Draw(Shape_i)}

https://godbolt.org/z/dTbqcMfrY
解释

  1. std::variant<Triangle, Circle> 是现代 C++ 的类型擦除手段
  2. std::visit 使用模板在编译期生成函数调用
  3. 无需虚函数,无额外运行时开销
  4. 可以直接在 TEST_F 单元测试中验证绘制操作

3⃣ 动态多态示例(虚函数)

#include<iostream>#include<vector>#include<memory>usingnamespace std;// ============================// 抽象接口(动态多态)// ============================structIShape{virtualvoiddraw()const=0;// 纯虚函数virtual~IShape()=default;// 虚析构};structTriangle:IShape{voiddraw()constoverride{ cout <<"△"<< endl;}};structCircle:IShape{voiddraw()constoverride{ cout <<"○"<< endl;}};// ============================// 单元测试// ============================#include<gtest/gtest.h>classDrawShapeDynamicTest:public::testing::Test{protected: vector<shared_ptr<IShape>> shapes;voidSetUp()override{ shapes.push_back(make_shared<Triangle>()); shapes.push_back(make_shared<Circle>());}};TEST_F(DrawShapeDynamicTest, DrawAllShapes){for(constauto& shape : shapes){ shape->draw();// 运行时多态,根据对象类型调用 draw()}// 数学公式表示:// Draw(Shape_i) = vtable[Shape_i] -> draw()// TotalDraw = Σ_{i=1}^{N} Draw(Shape_i)}

https://godbolt.org/z/c79rPMorh
解释

  1. 基类 IShape 提供统一接口
  2. 派生类实现具体方法
  3. 客户代码只依赖接口,符合 依赖倒置原则(DIP)
  4. 运行时通过 vtable 调用,实现动态多态

4⃣ 多态选择原则


多态类型使用场景优点缺点
静态多态编译期确定类型、性能敏感无运行时开销、高性能扩展性受限,模板报错复杂
动态多态运行时类型不确定、可扩展性高灵活,可扩展运行时开销,需内存管理

5⃣ 数学公式总结

  1. 静态多态(编译期分派):
    D r a w ( S h a p e i ) = { Triangle.draw() , if  S h a p e i = T r i a n g l e Circle.draw() , if  S h a p e i = C i r c l e Draw(Shape_i) = \begin{cases} \text{Triangle.draw()}, & \text{if } Shape_i = Triangle \\ \text{Circle.draw()}, & \text{if } Shape_i = Circle \end{cases} Draw(Shapei​)={Triangle.draw(),Circle.draw(),​if Shapei​=Triangleif Shapei​=Circle​
  2. 动态多态(运行时分派):
    D r a w ( S h a p e i ) = v t a b l e [ S h a p e i ] → d r a w ( ) Draw(Shape_i) = vtable[Shape_i] \rightarrow draw() Draw(Shapei​)=vtable[Shapei​]→draw()
  3. 总绘制量
    T o t a l D r a w = ∑ i = 1 N D r a w ( S h a p e i ) TotalDraw = \sum_{i=1}^{N} Draw(Shape_i) TotalDraw=i=1∑N​Draw(Shapei​)
  • N N N = 图形总数
  • D r a w ( S h a p e i ) Draw(Shape_i) Draw(Shapei​) = 多态调用的绘制操作

面向对象(OOP)设计 + 端口与适配器(Ports & Adapters / Hexagonal)架构风格

1 - *对象模型InterfaceADAPTERAPIAPIAPIMsgADAPTERRepoADAPTER依赖倒置端口与适配器模式选择性集成

1⃣ 图形结构解析

核心元素

  1. 六边形/多边形背景
    • SVG 中用两个多边形套叠(绿色虚线和粗线)表示系统边界或上下文边界。
    • 外层虚线可能代表“潜在扩展区域”,内层实线代表“当前系统核心”。
  2. 中心对象模型
    • 中间 对象模型 文本的矩形表示系统的核心领域模型(Domain Model)。
    • 它封装了数据和行为,是系统稳定的核心。
  3. 端口与适配器(Ports & Adapters)
    • 四周的小矩形标记了 API / Interface / ADAPTER,对应系统的接口和外部依赖。
    • 线条表示依赖关系:
      • 核心模型并不直接依赖外部实现,而是通过 接口/端口访问外部系统。
      • 外部实现(如 Msg, Repo)通过适配器实现这些接口。
  4. 文本注释
    • “依赖倒置”、“端口与适配器模式”、“选择性集成”明确了该架构遵循 依赖倒置原则,核心依赖抽象而非具体实现。

2⃣ 面向对象设计特征

优点:

  • 封装性:对象自封装数据和行为,有利于理解、复用。
  • 稳定性:对象是“设计质料”,便于在不同领域使用。
  • 多态性:可应对变化,提升软件可扩展性。
  • 模型优先:设计演进通过模型和结构的调整进行,而非直接修改实现。
    不足:
  • 逻辑分散:业务逻辑散落在对象内部,可能碎片化。
  • 行为与数据匹配问题
    • 贫血模型:对象只含数据,无行为。
    • 充血模型:对象包含过多行为,协调困难。
    • 可通过 DCI 架构解决。
  • 建模经验依赖强:理论支撑较弱,依赖工程师经验。

3⃣ 端口与适配器架构(Hexagonal)

  • 目标:弱化上下层依赖关系,使核心模型独立于技术实现。
  • 特点
    • 核心模型只依赖接口/端口。
    • 外部系统通过适配器实现接口,便于替换或扩展。
    • 支持“选择性集成”:不同外部服务可灵活接入。

关系示意

[外部系统] --> [适配器/Adapter] --> [接口/Port] --> [核心对象模型] 

4⃣ 设计启示

  1. 面向对象 + 依赖倒置的组合,是构建可维护、可扩展系统的经典方案。
  2. 端口与适配器模式使得:
    • 核心逻辑与技术实现解耦。
    • 系统更容易测试(可用 Mock / Stub 替代外部依赖)。
  3. 服务化或组件化扩展
    • 可选择性集成外部组件。
    • 支持渐进式重构和微服务迁移。

面向对象(OOP)优点分析

  1. 对象自封装数据和行为,利于理解和复用
    面向对象将数据(属性)和行为(方法)封装在同一个实体——对象中,这样可以把相关逻辑集中在一起,减少外部依赖。
    示例代码:
#include<iostream>#include<string>// 定义一个对象:PersonclassPerson{private: std::string name;// 数据封装int age;// 数据封装public:// 构造函数Person(const std::string& n,int a):name(n),age(a){}// 行为封装:展示信息voidshowInfo()const{ std::cout <<"姓名: "<< name <<", 年龄: "<< age << std::endl;}// 行为封装:年龄增加voidhaveBirthday(){ age +=1;}};intmain(){ Person p("张三",30); p.showInfo();// 输出:姓名: 张三, 年龄: 30 p.haveBirthday(); p.showInfo();// 输出:姓名: 张三, 年龄: 31}

解释:

  • 数据(nameage)和行为(showInfo()haveBirthday())都在Person对象内部。
  • 外部代码不直接操作数据,只通过对象方法访问,提高理解性和复用性。
  1. 对象作为“稳定的设计质料”,适合泛领域使用
    对象可以被视为构建系统的基本单元,就像积木一样,可以在不同场景中重用。
    示例:
classAccount{private:double balance;public:Account(double b):balance(b){}voiddeposit(double amount){ balance += amount;}voidwithdraw(double amount){ balance -= amount;}doublegetBalance()const{return balance;}};

这里的Account对象可以在银行系统、电商系统或游戏系统中复用。
3. 多态提高响应变化的能力
多态允许同一接口的对象在运行时表现不同的行为,提高系统灵活性,易于扩展。

classShape{public:virtualdoublearea()const=0;// 抽象接口};classCircle:publicShape{private:double r;public:Circle(double radius):r(radius){}doublearea()constoverride{return3.14159* r * r;}// 圆面积};classRectangle:publicShape{private:double w, h;public:Rectangle(double width,double height):w(width),h(height){}doublearea()constoverride{return w * h;}// 矩形面积};intmain(){ Shape* s1 =newCircle(2.0); Shape* s2 =newRectangle(3.0,4.0); std::cout << s1->area()<< std::endl;// 12.56636 std::cout << s2->area()<< std::endl;// 12}

公式示意:
总面积 = ∑ i = 1 n Shape i . area ( ) \text{总面积} = \sum_{i=1}^{n} \text{Shape}_i.\text{area}() 总面积=i=1∑n​Shapei​.area()
多态让我们只通过统一接口调用方法,而不关心具体对象类型。
4. 对设计的理解和演进优先是对模型和结构的理解和调整
面向对象强调“模型驱动”,开发者先理解业务对象和结构,再调整设计。相比直接写过程式代码,系统演进更有条理。

面向对象(OOP)不足分析

  1. 业务逻辑碎片化
    业务逻辑可能散落在多个对象中,导致整体业务流程难以直观理解。
    示例问题:
classOrder{...};classPayment{...};classShipment{...};// 业务逻辑要跨多个对象协调voidprocessOrder(Order& o, Payment& p, Shipment& s){ p.pay(o.amount); s.ship(o);}

逻辑分散在OrderPaymentShipment对象中,阅读者难以一次性理解业务流程。
2. 行为和数据失匹配协调(贫血模型 vs 充血模型)

  • 贫血模型(Anemic):对象只存数据,不封装行为,逻辑在外部函数。
  • 充血模型(Rich/Full):对象既存数据又封装行为。
    DCI架构(Data, Context, Interaction)尝试解决这个问题:把对象数据、角色上下文、行为交互区分开来,减少贫血对象。
  1. 建模依赖工程经验,缺乏严格理论支撑
    面向对象没有严格的数学基础建模方式(不像函数式有λ演算),设计高度依赖开发者的经验和直觉。
    示例公式化尝试:
    如果一个对象O封装数据 d d d和行为 f f f,可表示为:
    O = ⟨ d , f ( d ) ⟩ O = \langle d, f(d) \rangle O=⟨d,f(d)⟩
    但如何组合多个对象形成复杂系统仍依赖经验。

总结


优点不足
封装数据和行为,利于理解与复用业务逻辑可能碎片化
对象可复用,泛领域使用行为和数据可能失匹配(贫血/充血问题)
多态提高灵活性,易扩展依赖经验,缺乏理论支撑
关注模型和结构,易演进-

泛型编程(Generic Programming)详解

泛型编程的核心思想是 将类型的变化抽象化,通过参数化、接口契约和延迟绑定,实现对不同数据类型、行为和对象的统一处理。

1. 代码中的三种变化

  1. 数据变化
    • 不同对象可能包含不同属性或字段,代码需要处理的对象数据不同。
    • 示例:不同几何图形对象有不同的半径、边长等。
  2. 行为变化
    • 对同一对象,不同操作逻辑可能不同。
    • 示例:draw()方法在CircleRectangle中的实现不同。
  3. 类型变化
    • 同一算法或逻辑需要处理不同类型的对象。
    • 示例:排序算法可以对int[]float[]string[]执行。

2. 换个视角:三种变化都是数据变化

  • 泛型编程的思想:所有变化都可以抽象为数据的不同表现形式
  • 接口契约(Contract / Concept):规定数据或对象允许的操作,从而把变化部分标准化。

3. 泛型编程的三个核心步骤

  1. 将变化参数化
    • 把不同类型、行为或数据变化用参数表示(模板或泛型参数)。
  2. 将参数契约化
    • 明确参数必须遵守的操作规范(合同/契约)。
    • 例如:类型必须支持比较运算(==, !=)。
  3. 将契约接口化
    • 将契约暴露为接口,让客户只依赖接口编程。
    • 稳定部分:面向接口编程
    • 变化部分:面向接口扩展
  • 上下文通过组合不同契约实现,满足不同场景需求。

4. 泛型的能力

  • 变化数据化:可以把不同的数据变化封装、持有、传递、延迟调用
  • 间接性:通过参数化和接口化实现更强抽象能力
    公式化表示
    如果有一个泛型函数 F F F,它依赖类型 T T T 并满足契约 C C C,则可以表示为:
    F ( T ∣ C ) F(T \mid C) F(T∣C)
  • T T T 是数据类型
  • C C C 是类型契约(允许执行的操作)

5. 示例代码解析

// 泛型函数模板:processtemplate<EqualityComparable Equal>voidprocess(Object* obj[], size_t size, Handler& handler, Equal&& eq){// 对象数组进行处理,使用Handler封装行为for(size_t i =0; i < size;++i){ handler.handle(*obj[i]);// 调用Handler契约方法}}

注释解释:

  • EqualityComparable Equal:约束类型Equal必须支持==!=操作
  • Object* obj[]:指向对象数组
  • Handler& handler:用于处理对象行为的接口

对象契约(Object Contract)

structObject{voidaction();// 对象行为int a, b;// 数据属性};
  • Object 是一个契约:客户只关心 action() 方法和属性 a, b 的存在

Handler契约(Handler Contract)

classHandler{public:virtualvoidhandle(const Object& obj)=0;// 必须实现处理对象行为virtual~Handler(){}// 虚析构,支持多态};
  • Handler规定了如何处理对象,而具体处理逻辑可以有不同实现

类型契约(EqualityComparable Contract)

template<typenameT>conceptbool EqualityComparable =requires(T a, T b){{a == b}->bool;{a != b}->bool;{b == a}->bool;{b != a}->bool;};
  • EqualityComparable要求类型T必须支持对等比较操作
  • 这种契约化允许泛型函数安全操作不同类型的数据

6. 总结泛型编程思路

  1. 稳定部分:面向接口契约编程
  2. 变化部分:面向接口契约扩展
  3. 组合能力:上下文可选择满足契约的实现
    公式化概念:
    GenericFunction = StablePart + ∑ VariablePart i \text{GenericFunction} = \text{StablePart} + \sum \text{VariablePart}_i GenericFunction=StablePart+∑VariableParti​
  • StablePart:接口契约
  • VariablePart:不同实现组合

泛型编程与三类契约示例

1⃣ 对象契约(Object Contract)

概念:对象契约规定对象必须提供哪些数据和方法,客户只依赖契约,不关心具体实现。

示例:几何图形对象

#include<iostream>#include<vector>#include<cmath>// 对象契约:ShapestructShape{virtualdoublearea()const=0;// 面积方法virtualdoubleperimeter()const=0;// 周长方法virtualvoidinfo()const=0;// 显示信息virtual~Shape(){}};// 具体对象:圆classCircle:publicShape{private:double radius;public:Circle(double r):radius(r){}doublearea()constoverride{return3.14159* radius * radius;}doubleperimeter()constoverride{return2*3.14159* radius;}voidinfo()constoverride{ std::cout <<"Circle with radius "<< radius << std::endl;}};// 具体对象:矩形classRectangle:publicShape{private:double width, height;public:Rectangle(double w,double h):width(w),height(h){}doublearea()constoverride{return width * height;}doubleperimeter()constoverride{return2*(width + height);}voidinfo()constoverride{ std::cout <<"Rectangle "<< width <<"x"<< height << std::endl;}};

解释

  • Shape 是对象契约,规定了三个方法:area()perimeter()info()
  • 圆和矩形实现了这个契约,但内部数据不同 → 数据变化

2⃣ Handler契约(Handler Contract)

概念:Handler契约规定如何处理对象的行为,允许不同策略的扩展。

示例:形状处理器

// Handler契约classShapeHandler{public:virtualvoidhandle(const Shape& s)=0;// 必须实现处理方法virtual~ShapeHandler(){}};// 具体Handler:打印信息classPrintHandler:publicShapeHandler{public:voidhandle(const Shape& s)override{ s.info(); std::cout <<"Area: "<< s.area()<<", Perimeter: "<< s.perimeter()<< std::endl;}};// 具体Handler:计算总面积classAreaSumHandler:publicShapeHandler{private:double totalArea =0;public:voidhandle(const Shape& s)override{ totalArea += s.area();}doublegetTotalArea()const{return totalArea;}};

解释

  • ShapeHandler 是 Handler契约,规定必须实现 handle() 方法。
  • 不同处理策略可扩展 → 行为变化

3⃣ 类型契约(EqualityComparable Contract)

概念:类型契约规定泛型类型必须满足特定操作,保证泛型函数可以安全使用。

示例:泛型查找函数

#include<concepts>#include<string>#include<vector>#include<iostream>// 类型契约:EqualityComparabletemplate<typenameT>conceptEqualityComparable=requires(T a, T b){{a == b}-> std::convertible_to<bool>;{a != b}-> std::convertible_to<bool>;};// 泛型函数:查找重复元素template<EqualityComparable T>boolhasDuplicate(const std::vector<T>& data){for(size_t i =0; i < data.size();++i)for(size_t j = i+1; j < data.size();++j)if(data[i]== data[j])returntrue;returnfalse;}

解释

  • EqualityComparable 类型契约保证泛型类型可以比较相等或不等 → 类型变化
  • 泛型函数hasDuplicate可以处理不同类型(如 intstd::string)。

4⃣ 综合使用示例

intmain(){// 对象契约实例 std::vector<Shape*> shapes ={newCircle(2.0),newRectangle(3.0,4.0)};// Handler契约实例 PrintHandler printHandler; AreaSumHandler areaHandler;// 处理每个Shapefor(auto s : shapes){ printHandler.handle(*s);// 打印 areaHandler.handle(*s);// 累加面积} std::cout <<"Total area: "<< areaHandler.getTotalArea()<< std::endl;// 类型契约实例 std::vector<int> nums ={1,2,3,4,2}; std::vector<std::string> strs ={"apple","banana","apple"}; std::cout <<"nums has duplicate? "<<hasDuplicate(nums)<< std::endl; std::cout <<"strs has duplicate? "<<hasDuplicate(strs)<< std::endl;// 释放对象for(auto s : shapes)delete s;}

输出示意:

Circle with radius 2 Area: 12.56636, Perimeter: 12.56636 Rectangle 3x4 Area: 12, Perimeter: 14 Total area: 24.56636 nums has duplicate? 1 strs has duplicate? 1 

5⃣ 总结契约与变化类型

契约类型变化类型示例核心概念
对象契约数据变化Shape / Circle / Rectangle对象必须提供哪些方法和数据
Handler契约行为变化ShapeHandler / PrintHandler / AreaSumHandler如何处理对象的行为
类型契约类型变化EqualityComparable / 泛型函数 hasDuplicate泛型类型必须满足操作要求
概念理解
  1. 数据变化 → 对象契约封装不同属性
  2. 行为变化 → Handler契约定义不同处理方式
  3. 类型变化 → 类型契约约束泛型类型,保证算法安全
#include<iostream>#include<vector>#include<cmath>// 对象契约:ShapestructShape{virtualdoublearea()const=0;// 面积方法virtualdoubleperimeter()const=0;// 周长方法virtualvoidinfo()const=0;// 显示信息virtual~Shape(){}};// 具体对象:圆classCircle:publicShape{private:double radius;public:Circle(double r):radius(r){}doublearea()constoverride{return3.14159* radius * radius;}doubleperimeter()constoverride{return2*3.14159* radius;}voidinfo()constoverride{ std::cout <<"Circle with radius "<< radius << std::endl;}};// 具体对象:矩形classRectangle:publicShape{private:double width, height;public:Rectangle(double w,double h):width(w),height(h){}doublearea()constoverride{return width * height;}doubleperimeter()constoverride{return2*(width + height);}voidinfo()constoverride{ std::cout <<"Rectangle "<< width <<"x"<< height << std::endl;}};// Handler契约classShapeHandler{public:virtualvoidhandle(const Shape& s)=0;// 必须实现处理方法virtual~ShapeHandler(){}};// 具体Handler:打印信息classPrintHandler:publicShapeHandler{public:voidhandle(const Shape& s)override{ s.info(); std::cout <<"Area: "<< s.area()<<", Perimeter: "<< s.perimeter()<< std::endl;}};// 具体Handler:计算总面积classAreaSumHandler:publicShapeHandler{private:double totalArea =0;public:voidhandle(const Shape& s)override{ totalArea += s.area();}doublegetTotalArea()const{return totalArea;}};#include<concepts>#include<string>#include<vector>#include<iostream>// 类型契约:EqualityComparabletemplate<typenameT>conceptEqualityComparable=requires(T a, T b){{a == b}-> std::convertible_to<bool>;{a != b}-> std::convertible_to<bool>;};// 泛型函数:查找重复元素template<EqualityComparable T>boolhasDuplicate(const std::vector<T>& data){for(size_t i =0; i < data.size();++i)for(size_t j = i+1; j < data.size();++j)if(data[i]== data[j])returntrue;returnfalse;}intmain(){// 对象契约实例 std::vector<Shape*> shapes ={newCircle(2.0),newRectangle(3.0,4.0)};// Handler契约实例 PrintHandler printHandler; AreaSumHandler areaHandler;// 处理每个Shapefor(auto s : shapes){ printHandler.handle(*s);// 打印 areaHandler.handle(*s);// 累加面积} std::cout <<"Total area: "<< areaHandler.getTotalArea()<< std::endl;// 类型契约实例 std::vector<int> nums ={1,2,3,4,2}; std::vector<std::string> strs ={"apple","banana","apple"}; std::cout <<"nums has duplicate? "<<hasDuplicate(nums)<< std::endl; std::cout <<"strs has duplicate? "<<hasDuplicate(strs)<< std::endl;// 释放对象for(auto s : shapes)delete s;}

https://godbolt.org/z/EzWdnP3vn

从泛型编程到编译期计算

从泛型编程到编译期计算(Compile-Time Computation)

C++ 中存在 两种计算阶段

  1. 编译期计算(Compile-Time)
    • 通过模板元编程(Template Meta Programming, TMP)、constexpr 等技术在编译期计算结果。
    • 优点:生成更高效的代码、提前检测错误、实现类型安全和静态验证。
  2. 运行期计算(Run-Time)
    • 普通 C++ 代码在程序执行时才计算结果。
      概念示意公式化
      C++ Program = Compile-Time Computation + Run-Time Computation \text{C++ Program} = \text{Compile-Time Computation} + \text{Run-Time Computation} C++ Program=Compile-Time Computation+Run-Time Computation

1⃣ 模板元编程(Template Meta Programming, TMP)

模板不仅可以生成类型,还可以在编译期执行逻辑操作。

示例:类型列表去重

#include<type_traits>#include<iostream>// 假设 holo 库提供 type_list_t 和 unique 元函数namespace holo {template<typename... Ts>structtype_list_t{};// 类型列表契约// 简单示意unique实现(去重逻辑略)template<typenameList>structunique;template<typename... Ts>using unique_t =typenameunique<type_list_t<Ts...>>::type;}// 测试用例TEST_CASE("unique"){constexprauto result = holo::type_list_t<int,short,longlong,short,char,int,longlong,char,short>{}| holo::unique();static_assert(result == holo::type_list_t<int,short,longlong,char>{});// 编译期验证}

注释解析:

  1. type_list_t<int, short, long long, ...> → 编译期类型列表
  2. holo::unique() → 编译期计算函数(模板元函数),去掉重复类型
  3. static_assert → 编译期断言,确保去重结果正确
    公式化表示
    unique ( type_list_t [ T 1 , T 2 , . . . , T n ] ) = type_list_t [ U 1 , U 2 , . . . , U m ] \text{unique}(\text{type\_list\_t}[T_1, T_2, ..., T_n]) = \text{type\_list\_t}[U_1, U_2, ..., U_m] unique(type_list_t[T1​,T2​,...,Tn​])=type_list_t[U1​,U2​,...,Um​]
  • T i T_i Ti​:原始类型列表
  • U i U_i Ui​:去重后的类型列表

2⃣ constexpr 与编译期字符串匹配

C++20 引入 consteval / constexpr / constinit 支持编译期计算,可在编译阶段处理字符串和正则表达式。

示例:CTRE 编译期正则匹配

#include<ctre.hpp>// 编译期正则库#include<string_view>#include<iostream>constexprauto pattern = ctll::fixed_string{"[a-z]+([0-9]+)"};// 编译期正则模式constexprboolmatch(std::string_view sv)noexcept{return ctre::match<pattern>(sv);// 编译期匹配}intmain(){static_assert(match("abc123"),"Pattern must match");// 编译期验证static_assert(!match("123abc"),"Pattern must not match"); std::cout <<"Compile-time regex checks passed!"<< std::endl;}

https://godbolt.org/z/4Td4bf98E

在这里插入图片描述

注释解析:

  1. ctll::fixed_string{"[a-z]+([0-9]+)"} → 编译期正则表达式
  2. ctre::match<pattern>(sv) → 在编译期计算匹配结果
  3. static_assert → 编译期断言结果,保证字符串符合模式
    特点
  • 正则表达式模式在编译期固定 → 生成高效代码
  • 不依赖运行期解析,提高性能
    公式化表示
    match ( pattern , string ) = { true , 如果字符串符合模式 false , 否则 \text{match}(\text{pattern}, \text{string}) = \begin{cases} \text{true}, & \text{如果字符串符合模式} \\ \text{false}, & \text{否则} \end{cases} match(pattern,string)={true,false,​如果字符串符合模式否则​

3⃣ 总结:从泛型到编译期计算的演进


阶段技术手段功能优势
泛型编程模板、概念(concepts)类型参数化,算法复用提前检测类型错误,提高抽象能力
编译期计算constexpr、模板元函数、CTRE类型列表操作、字符串匹配、逻辑计算编译期生成结果,提高效率、减少运行期开销

核心思想

  1. 变化参数化 → 泛型模板
  2. 契约约束 → concept / 类型契约
  3. 编译期计算constexpr / TMP / consteval
  4. 运行期组合 → 根据上下文使用编译期计算结果
    总结公式
    泛型 + 编译期计算的组合:
    Program = Compile-Time Result ( Template/Concept ) + Run-Time Logic \text{Program} = \text{Compile-Time Result}(\text{Template/Concept}) + \text{Run-Time Logic} Program=Compile-Time Result(Template/Concept)+Run-Time Logic
  • 编译期生成稳定、安全的类型与结果
  • 运行期组合灵活,减少重复计算
#include<catch2/catch_test_macros.hpp>#include<catch2/catch_session.hpp>#include<type_traits>#include<iostream>#include<string_view>namespace holo {// 1. 类型列表容器template<typename... Ts>structtype_list_t{staticconstexpr std::size_t size =sizeof...(Ts);template<typename... Us>constexprbooloperator==(type_list_t<Us...>)const{return std::is_same_v<type_list_t<Ts...>, type_list_t<Us...>>;}template<typename... Us>constexprbooloperator!=(type_list_t<Us...> o)const{return!(*this== o);}};// 2. 判断 T 是否已在列表中(改名 contains_impl,避免与标准库冲突)template<typenameT,typename... Ts>structcontains_impl:std::disjunction<std::is_same<T,Ts>...>{};template<typenameT,typename... Ts>inlineconstexprbool contains_v = contains_impl<T, Ts...>::value;// 3. 如果 T 不在 List 中则追加template<typenameT,typenameList>structpush_back_if_new;template<typenameT,typename... Us>structpush_back_if_new<T, type_list_t<Us...>>{using type = std::conditional_t< contains_v<T, Us...>, type_list_t<Us...>, type_list_t<Us..., T>>;};// 4. 递归去重实现(internal)template<typenameInput,typenameOutput>structunique_impl;template<typenameOutput>structunique_impl<type_list_t<>, Output>{using type = Output;};template<typenameHead,typename... Tail,typenameOutput>structunique_impl<type_list_t<Head, Tail...>, Output>{using next =typenamepush_back_if_new<Head, Output>::type;using type =typenameunique_impl<type_list_t<Tail...>, next>::type;};// 5. 对外 using 别名template<typename... Ts>using unique_t =typenameunique_impl<type_list_t<Ts...>, type_list_t<>>::type;// 6. 管道操作对象(函数对象,不是 struct 模板,不与 unique() 冲突)structunique_fn{template<typename... Ts>constexprautooperator()(type_list_t<Ts...>)const{return unique_t<Ts...>{};}};// 7. operator| 支持管道语法template<typename... Ts>constexprautooperator|(type_list_t<Ts...> list, unique_fn fn){returnfn(list);}// 8. unique() 工厂函数 —— 关键:名字不再与 struct unique 冲突inlineconstexpr unique_fn unique()noexcept{return{};}// 9. 调试:打印类型列表template<typenameT>constexpr std::string_view type_name(){#ifdefined(__clang__)||defined(__GNUC__) std::string_view p = __PRETTY_FUNCTION__;auto s = p.find("T = ")+4, e = p.rfind(']');return p.substr(s, e - s);#elsereturn"unknown";#endif}template<typename... Ts>voidprint_list(type_list_t<Ts...>, std::string_view label =""){if(!label.empty()) std::cout << label <<": "; std::cout <<"type_list_t<"; std::size_t i =0;((std::cout <<(i++?", ":"")<<type_name<Ts>()),...); std::cout <<"> size="<<sizeof...(Ts)<<"\n";}}// namespace holo// ============================================================// Catch2 测试// ============================================================TEST_CASE("unique - basic dedup","[unique]"){constexprauto result = holo::type_list_t<int,short,longlong,short,char,int,longlong,char,short>{}| holo::unique();static_assert(result == holo::type_list_t<int,short,longlong,char>{}); holo::print_list( holo::type_list_t<int,short,longlong,short,char,int,longlong,char,short>{},"输入"); holo::print_list(result,"去重后");CHECK(result == holo::type_list_t<int,short,longlong,char>{});}TEST_CASE("unique - already unique","[unique]"){constexprauto result = holo::type_list_t<int,double,char>{}| holo::unique();static_assert(result == holo::type_list_t<int,double,char>{});CHECK(result == holo::type_list_t<int,double,char>{}); holo::print_list(result,"无重复");}TEST_CASE("unique - empty list","[unique]"){constexprauto result = holo::type_list_t<>{}| holo::unique();static_assert(result == holo::type_list_t<>{});CHECK(result == holo::type_list_t<>{});}TEST_CASE("unique - single element","[unique]"){constexprauto result = holo::type_list_t<float>{}| holo::unique();static_assert(result == holo::type_list_t<float>{});CHECK(result == holo::type_list_t<float>{});}TEST_CASE("unique - all same type","[unique]"){constexprauto result = holo::type_list_t<int,int,int,int>{}| holo::unique();static_assert(result == holo::type_list_t<int>{});CHECK(result == holo::type_list_t<int>{}); holo::print_list(result,"全相同");}TEST_CASE("unique - size check","[unique]"){using output = holo::unique_t<int,float,int,double,float>;static_assert(output::size ==3);static_assert(std::is_same_v<output, holo::type_list_t<int,float,double>>);CHECK(output::size ==3);}TEST_CASE("unique - preserves order","[unique]"){constexprauto result = holo::type_list_t<char,int,char,double,int>{}| holo::unique();static_assert(result == holo::type_list_t<char,int,double>{});CHECK(result == holo::type_list_t<char,int,double>{}); holo::print_list(result,"顺序保留");}intmain(int argc,char* argv[]){ std::cout <<"=== holo::unique 编译期类型去重 ===\n\n";returnCatch::Session().run(argc, argv);}

https://godbolt.org/z/TYPjrPehc

函数式编程

函数式编程(Functional Programming)详解

函数式编程是一种 以函数为核心的程序设计范式,强调数据不可变、函数纯粹、无副作用,程序由数据和函数的映射组成。

1⃣ 核心概念

    • 函数是“一等公民”,可以像数据一样传递、赋值、作为参数或返回值。
    • 示例:
    • 对相同输入,总是返回相同输出
    • 无副作用(不修改外部状态、不依赖外部变量)
    • 公式化:
      f ( x ) = y 且  ∀ x , f ( x ) 恒等返回同一  y f(x) = y \quad \text{且 } \forall x, f(x) \text{恒等返回同一 } y f(x)=y且 ∀x,f(x)恒等返回同一 y
      示例:
  1. No Side Effects(无副作用)
    • 函数不改变外部变量、不进行 I/O、日志或状态修改
    • 有利于并行计算和可预测性
    • 数据一旦创建就不可改变
    • 修改数据时返回新数据,而不是改变原数据
  2. Lazy Evaluation(惰性求值)
    • 表达式延迟计算,直到结果真正需要时才计算
    • 有助于处理无限序列或减少不必要计算
  3. Statelessness(无状态)
    • 函数不依赖或修改外部状态
    • 可以安全地并行执行

Immutable Data(不可变数据)

#include<vector>#include<algorithm> std::vector<int>addOne(const std::vector<int>& v){ std::vector<int> result = v; std::transform(result.begin(), result.end(), result.begin(),[](int x){return x +1;});return result;// 返回新向量,原向量不变}

Pure Functions(纯函数)

intsquare(int x){return x * x;}// 纯函数

First-Class Functions(头等函数)

#include<iostream>#include<functional>intadd(int x,int y){return x + y;}intmain(){ std::function<int(int,int)> f = add;// 函数赋值给变量 std::cout <<f(3,4)<< std::endl;// 输出7}

2⃣ 函数式设计思想公式化

函数式设计可以理解为:
程序 = 数据 + 映射(函数) \text{程序} = \text{数据} + \text{映射(函数)} 程序=数据+映射(函数)
更具体公式:
Program ( D ) = F ( D ) \text{Program}(D) = F(D) Program(D)=F(D)

  • D D D:输入数据
  • F F F:函数映射
  • F ( D ) F(D) F(D):输出结果
    如果有多个函数组合:
    F 3 ( F 2 ( F 1 ( D ) ) ) F_3(F_2(F_1(D))) F3​(F2​(F1​(D)))
  • 表示数据经过多个函数映射的流水线处理

3⃣ C++ 函数式示例

示例 1:纯函数 + 高阶函数

#include<iostream>#include<vector>#include<algorithm>#include<numeric>intmain(){ std::vector<int> data ={1,2,3,4,5};// 高阶函数:接受函数作为参数auto square =[](int x){return x*x;}; std::vector<int>result(data.size()); std::transform(data.begin(), data.end(), result.begin(), square);// 输出结果for(auto x: result) std::cout << x <<" ";// 1 4 9 16 25 std::cout << std::endl;// 组合函数auto sum = std::accumulate(result.begin(), result.end(),0); std::cout <<"Sum of squares: "<< sum << std::endl;// 55}

解释

  • square 是纯函数,无副作用
  • std::transform 是高阶函数,把函数映射应用于数据
  • std::accumulate 对结果进行函数组合

示例 2:惰性求值(生成器风格)

C++20 ranges 提供惰性视图(lazy evaluation):

#include<iostream>#include<ranges>intmain(){auto nums = std::views::iota(1,100)// 生成 1~99 的序列| std::views::filter([](int x){return x %2==0;})// 只保留偶数| std::views::transform([](int x){return x*x;});// 平方for(int x : nums | std::views::take(5))// 惰性取前5个 std::cout << x <<" ";// 输出: 4 16 36 64 100}

解释

  • iota → 无限序列
  • filter → 惰性过滤
  • transform → 惰性映射
  • 直到 take 真正需要值时才计算

4⃣ 总结函数式设计特点


特性描述C++示例
First-Class Functions函数像数据一样传递std::function, lambda
Pure Functions相同输入输出相同,无副作用int square(int x)
Immutable不修改原数据,返回新数据addOne() 返回新向量
Lazy Evaluation延迟计算,按需求值ranges::views
Statelessness函数无外部状态依赖高阶函数组合

函数式编程强调:
程序的核心 = 数据不可变 + 函数映射 \text{程序的核心} = \text{数据不可变} + \text{函数映射} 程序的核心=数据不可变+函数映射
通过纯函数、不可变数据和函数组合,可以得到 可预测、可组合、可并行的程序

C++ Lambda 与闭包(Closure)详解

C++ 中的 Lambda 表达式是一种简化闭包(Closure)创建的方法。闭包本质上是一个 自封装的对象,携带私有状态、捕获上下文,提供单一接口。

1⃣ 客户期望的调用接口原型

假设我们有一个函数 process,希望客户传入一个可调用对象(函数、Lambda 或 Functor):

#include<functional>#include<iostream>voidprocess(std::function<int(int)> accumulator){int ret =accumulator(5);// 调用客户提供的函数 std::cout <<"Result: "<< ret << std::endl;}

解释

  • std::function<int(int)> 是接口契约(Contract):要求任何可调用对象都必须接受一个 int 参数并返回 int
  • process 只依赖接口,不关心具体实现

2⃣ Functor(函数对象)实现闭包

闭包本质是一个 自封装对象

classFunctor{private:int member;// 捕获上下文的私有状态public:Functor(int t_context):member(t_context){}// 构造函数捕获上下文intoperator()(int parameter){// 实现调用接口return member + parameter;// 利用捕获的上下文}};intmain(){int context =10;// 上下文环境 Functor f(context);// 创建闭包实例process(f);// 动态传递,符合接口契约}

解释

  • Functor 封装了私有状态 member
  • 提供单一接口 operator()
  • 捕获上下文环境 context
  • 可以动态传递给函数 process 使用
    公式化表示
    Closure = ⟨ CapturedContext , FunctionInterface ⟩ \text{Closure} = \langle \text{CapturedContext}, \text{FunctionInterface} \rangle Closure=⟨CapturedContext,FunctionInterface⟩
  • CapturedContext → 上下文状态
  • FunctionInterface → 可调用接口

3⃣ Lambda 表达式实现闭包

Lambda 是 低成本的闭包语法糖

intmain(){int context =10;// 声明并捕获上下文 contextauto lambda =[context](int x)->int{return x + context;// 利用捕获的上下文};process(lambda);// 传递 Lambda,满足接口契约}

解释 Lambda 语法

[capture](parameters)mutable?->return-type { statements }
  • [capture] → 捕获外部上下文,可以是值捕获 [x] 或引用捕获 [&x]
  • (parameters) → 函数参数列表
  • mutable → 如果捕获的是值且需要在函数体修改捕获变量
  • -> return-type → 可选返回类型
  • { statements } → 函数体

4⃣ Lambda 与闭包特性总结


特性描述示例
捕获上下文Lambda 可以捕获外部变量[context](int x){ return x+context; }
接口契约提供单一可调用接口int operator()(int) / Lambda (int)->int
独立生命周期Lambda 对象生命周期独立可作为对象传递给函数
可组合性可以与其他 Lambda 或函数组合高阶函数、函数管道
自封装私有状态捕获变量成为 Lambda 内部私有成员[context] 实现闭包存储

5⃣ Lambda vs Functor vs 对象

  • Lambda 是 轻量闭包
  • Functor 是 手动实现闭包对象
  • 对象(如 Service Object)是“穷人的闭包”
    比较表

特性LambdaFunctor普通对象
捕获上下文×
接口单一多接口可选
生命周期独立由对象管理
使用成本
可组合性视设计而定

6⃣ 高阶组合示例

intmain(){int a =3, b =7;auto add_a =[a](int x){return x + a;};// 捕获 aauto multiply_b =[b](int x){return x * b;};// 捕获 bauto combined =[add_a, multiply_b](int x){returnmultiply_b(add_a(x));// 高阶组合}; std::cout <<combined(5)<< std::endl;// (5 + 3) * 7 = 56}

https://godbolt.org/z/x11s1dz4v
公式化表示闭包组合
CombinedClosure ( x ) = f ( g ( x ) ) \text{CombinedClosure}(x) = f(g(x)) CombinedClosure(x)=f(g(x))

  • g → 内部 Lambda,捕获上下文 a
  • f → 外部 Lambda,捕获上下文 b

7⃣ 总结

  1. Lambda 是 低成本闭包实现
  2. 闭包核心:
    • 捕获上下文
    • 单一接口
    • 自封装私有状态
    • 独立生命周期
    • 可组合
  3. 使用 Lambda 时:
    • 遵循接口契约
    • 避免滥用,保持功能级复用和资源隔离
      核心公式化概念
      Closure = ⟨ CapturedContext , FunctionInterface ⟩ \text{Closure} = \langle \text{CapturedContext}, \text{FunctionInterface} \rangle Closure=⟨CapturedContext,FunctionInterface⟩
      CombinedClosure ( x ) = f ( g ( x ) ) 闭包组合 \text{CombinedClosure}(x) = f(g(x)) \quad \text{闭包组合} CombinedClosure(x)=f(g(x))闭包组合

函数式设计:高阶函数与组合(抽象代数视角)

函数式编程(FP)的核心是 函数优先,强调 函数组合不可变数据纯函数
通过闭包和高阶函数可以实现 单一接口的标准化数据映射规则串联,从而完成数据从源到结果的转换。

1⃣ 面向对象原则对比


OO原则/模式核心思想FP对比
单一职责原则 (SRP)一个类只负责一件事函数天然单一接口,职责自然分离
开放封闭原则 (OCP)对扩展开放,对修改封闭函数组合与高阶函数可以增加功能而不修改原函数
依赖倒置原则 (DIP)依赖抽象接口而非具体实现函数接口/闭包契约提供自然抽象
接口隔离原则 (ISP)客户端不依赖不使用的方法高阶函数提供单一接口,无多余方法
工厂模式动态生成对象高阶函数可以动态生成函数组合
策略模式可替换算法函数组合和闭包可实现策略切换
装饰器模式动态增加功能高阶函数可包装其他函数
访问者模式数据操作分离函数组合可以对数据流逐步处理

总结:在 FP 中,一切皆函数,高阶函数和闭包提供了天然的 组合能力,实现 OO 中的大多数设计模式和原则。

2⃣ 高阶函数与闭包组合示例

2.1 高阶函数定义

高阶函数(Higher-Order Function, HOF)指 接受函数作为参数或返回函数的函数

#include<iostream>#include<functional>#include<vector>#include<algorithm>// 高阶函数:函数组合auto compose =[](auto f,auto g){return[f,g](auto x){returnf(g(x));};// 组合函数 f(g(x))};// 单一职责函数:平方auto square =[](int x){return x*x;};// 单一职责函数:加一auto addOne =[](int x){return x+1;};intmain(){auto squareThenAddOne =compose(addOne, square);// 高阶函数组合int result =squareThenAddOne(3);// (3*3)+1 = 10 std::cout <<"Result: "<< result << std::endl;}

解释

  • compose(f,g) → 高阶函数,将两个函数组合
  • squareaddOne → 单一职责函数(闭包)
  • squareThenAddOne → 新闭包,映射规则串联
    公式化

    compose ( f , g ) ( x ) = f ( g ( x ) ) \text{compose}(f, g)(x) = f(g(x)) compose(f,g)(x)=f(g(x))

2.2 数据流映射示例

假设有数据源,需要经过多步规则转换:

#include<vector>#include<ranges>#include<iostream>intmain(){ std::vector<int> data ={1,2,3,4,5};auto multiply2 =[](int x){return x *2;};auto subtract2 =[](int x){return x -2;};auto filter =[](int x){return x %4==0;};// 高阶函数组合 + 惰性过滤auto pipeline = data | std::views::transform(multiply2)// 第一步映射 | std::views::transform(subtract2)// 第二步映射| std::views::filter(filter);// 第三步规则for(auto x : pipeline) std::cout << x <<" ";// 输出:0 4 8 }

解释

  • 数据 data → 映射 multiply2 → 映射 subtract2 → 过滤 filter
  • 闭包+高阶函数+组合 → 数据从源到结果的映射描述
  • 每一步都是纯函数,没有副作用,数据不可变
    公式化
    Result = filter ∗ filter ∘ map ∗ subtract2 ∘ map multiply2 ( D ) \text{Result} = \text{filter}*{\text{filter}} \circ \text{map}*{\text{subtract2}} \circ \text{map}_{\text{multiply2}}(D) Result=filter∗filter∘map∗subtract2∘mapmultiply2​(D)

3⃣ 抽象代数视角

FP 中,函数组合可以类比 抽象代数

  • 函数空间 (F)
  • 组合运算 (\circ) 满足结合律:
    f ∘ ( g ∘ h ) = ( f ∘ g ) ∘ h f \circ (g \circ h) = (f \circ g) \circ h f∘(g∘h)=(f∘g)∘h
  • 单一接口函数是封闭的元素,可以任意组合

4⃣ FP设计思想总结

  1. 一切皆函数 → 单一职责,接口契约
  2. 闭包捕获上下文 → 保存状态、支持高阶函数
  3. 高阶函数组合 → 数据从源到结果的映射流水线
  4. 可组合性 → 类似 OO 模式的策略、装饰器、工厂等
  5. 纯函数 + 不可变数据 + 惰性求值 → 可预测、可复用、可并行
    核心公式化概念
    Program = F n ∘ F n − 1 ∘ ⋯ ∘ F 1 ( D ) \text{Program} = F_n \circ F_{n-1} \circ \dots \circ F_1 (D) Program=Fn​∘Fn−1​∘⋯∘F1​(D)
  • D D D → 数据源
  • F i F_i Fi​ → 高阶函数组合的单一职责函数(闭包)
  • 输出 → 数据映射结果

借鉴函数式设计的架构风格解析

函数式设计强调:

  1. 数据不可变 + 函数映射 → 事件驱动与状态不可变理念
  2. 单一接口与高阶函数组合 → 服务、函数、处理器可组合
  3. 无副作用 + 可组合 → 可并行、易扩展
    在现代架构中,这些思想体现为:
  • Event Sourcing:所有状态变化都记录为事件,状态可由事件回放计算得到 → 数据不可变
  • Reactive Architecture:响应式、异步、事件流处理 → 函数组合处理数据流
  • Lambda Architecture / FaaS / Serverless:函数级服务、按需执行 → 高阶函数思想落地

1⃣ Event Sourcing 架构解析

Event Sourcing

Command

Command

Event

Event

Read Model

Query

User Interface

Command Bus

Command Handler

Repository

Domain objects

Event Store

Event Bus

Event Handler

Read Database

Query Processing

理解与注释

  1. User Interface (UI)
    • 前端用户操作 → 命令或查询 → 数据流入口
    • 对应函数式数据源 D D D
  2. Command Bus & Command Handler
    • 命令总线和处理器 → 高阶函数的“函数组合”
    • 接收命令,处理业务逻辑
    • 可以封装闭包上下文(Domain Objects)
  3. Repository & Domain Objects
    • 状态不可变,所有修改生成事件 → 对应 FP 中 不可变数据 + 映射函数
    • 域对象是状态和行为的组合(对象契约),事件记录是数据不可变的体现
  4. Event Bus
    • 函数组合/高阶函数的异步执行机制
    • 将事件传播到各个处理器
  5. Read Side (Query Processing)
    • 查询数据库 → 映射函数组合,生成可视化结果
    • 对应 FP 中 纯函数 + 映射流水线
      公式化表示 Event Sourcing 数据流

      D UI → C o m m a n d H a n d l e r E → E v e n t B u s F ReadSide ( E ) = R e s u l t D_{\text{UI}} \xrightarrow{CommandHandler} E \xrightarrow{EventBus} F_{\text{ReadSide}}(E) = Result DUI​CommandHandler​EEventBus​FReadSide​(E)=Result

C++ / 函数式风格 + Lambda / 高阶函数 + Event Sourcing 思想 写一个可运行的示例

1⃣ 基础命令与事件类型

#include<iostream>#include<vector>#include<functional>#include<string>// 命令类型(Command)structCommand{ std::string name;int payload;};// 事件类型(Event)structEvent{ std::string name;int payload;};

解释

  • Command → 类似 UI 发出的指令
  • Event → 命令处理结果,状态不可变
  • 对应 FP 数据流: D Input → C o m m a n d H a n d l e r E D_{\text{Input}} \xrightarrow{CommandHandler} E DInput​CommandHandler​E

2⃣ 高阶函数 Command Handler

using CommandHandler = std::function<std::vector<Event>(const Command&)>;// 示例闭包 CommandHandler CommandHandler purchaseHandler(int multiplier){// multiplier 捕获上下文,闭包封装状态return[multiplier](const Command& cmd)-> std::vector<Event>{ std::vector<Event> events;int value = cmd.payload * multiplier;// 业务逻辑 events.push_back({"PurchaseProcessed", value});return events;};}

解释

  • purchaseHandler → 高阶函数,返回 闭包
  • 捕获上下文 multiplier → 独立生命周期
  • 接口契约:输入 Command → 输出 std::vector<Event>
    公式化:
    purchaseHandler ( m ) ( C o m m a n d ) → EventList \text{purchaseHandler}(m)(Command) \rightarrow \text{EventList} purchaseHandler(m)(Command)→EventList

3⃣ Event Bus 与 Query Pipeline

using EventHandler = std::function<void(const Event&)>;// 简单 Event BusvoideventBus(const std::vector<Event>& events,const EventHandler& handler){for(constauto& e : events){handler(e);// 异步或流水线处理可扩展}}// 查询处理器(Read Side)auto queryProcessor =[](const Event& e){ std::cout <<"ReadModel updated: "<< e.name <<" -> "<< e.payload << std::endl;};

解释

  • eventBus → 类似 Event Bus / 高阶函数组合
  • queryProcessor → Read Side,纯函数映射事件到结果
    公式:
    F Query ( E i ) = R e s u l t i F_{\text{Query}}(E_i) = Result_i FQuery​(Ei​)=Resulti​

4⃣ 完整数据流示例

intmain(){// UI 发送命令 Command cmd{"Purchase",100};// 创建 CommandHandler 闭包auto handler =purchaseHandler(2);// multiplier = 2// 执行命令 → 生成事件(Event Sourcing) std::vector<Event> events =handler(cmd);// 事件总线异步分发eventBus(events, queryProcessor);return0;}

https://godbolt.org/z/qdWbnzxWq

输出示例:

ReadModel updated: PurchaseProcessed -> 200 

解释

  1. UI 发出命令 → Command cmd
  2. CommandHandler 闭包处理 → 生成事件 → events
  3. EventBus 分发事件 → 查询处理器更新 Read Model
    整体函数式映射公式
    D UI → C o m m a n d H a n d l e r closure E → E v e n t B u s ∘ Q u e r y P r o c e s s o r R e s u l t D_{\text{UI}} \xrightarrow{CommandHandler_{\text{closure}}} E \xrightarrow{EventBus \circ QueryProcessor} Result DUI​CommandHandlerclosure​​EEventBus∘QueryProcessor​Result

5⃣ 可扩展组合

可以增加更多高阶函数和闭包组合策略:

auto discountHandler =[](int discount){return[discount](const Command& cmd){int value = cmd.payload - discount;return std::vector<Event>{{"DiscountApplied", value}};};};intmain(){ Command cmd{"Purchase",100};auto handler =purchaseHandler(2);auto discount =discountHandler(10);// 多闭包组合(函数式流水线) std::vector<Event> events1 =handler(cmd); std::vector<Event> events2 =discount(cmd); events1.insert(events1.end(), events2.begin(), events2.end());eventBus(events1, queryProcessor);}

https://godbolt.org/z/vxvWjf51v

输出:

ReadModel updated: PurchaseProcessed -> 200 ReadModel updated: DiscountApplied -> 90 

解释

  • 多个闭包高阶函数组合 → 函数式流水线
  • 数据不可变 → 每个闭包生成新事件
  • 可类比 OO 策略模式、装饰器模式

2⃣ Serverless / FaaS 架构解析

1

2

3

4

5

Client
browser

Authentication
Service

API Gateway

Purchase
Function

Search
Function

Product
Database

Purchase
Database

理解与注释

  1. Client → Gateway → Function
    • 函数式架构风格:每个服务都是一个闭包 + 单一接口
    • FaaS 函数相当于 FP 中的高阶函数
    • Lambda 表达式式的函数封装了上下文和输入
  2. Authentication Service
    • 封装了验证逻辑 → 可复用闭包
    • 接口契约明确定义输入/输出
  3. Database / Storage
    • 数据源不可变或通过事件更新 → FP 数据不可变理念
  4. 组合流水线
    • Gateway 将请求路由到不同函数 → 类似函数组合管道
    • Purchase Function、Search Function 独立处理 → 单一职责函数
      公式化表示 Serverless 数据流
      C l i e n t → G a t e w a y F Purchase/Query ( D Request ) → D B Client \xrightarrow{Gateway} F_{\text{Purchase/Query}}(D_{\text{Request}}) \rightarrow DB ClientGateway​FPurchase/Query​(DRequest​)→DB
  • F F F → 独立函数(闭包)
  • D Request D_{\text{Request}} DRequest​ → 输入数据
  • DB → 状态存储

3⃣ 函数式架构总结

  1. 单一接口与闭包封装
    • 每个函数/服务像 FP 的闭包 → 独立状态、单一接口、可组合
  2. 高阶函数组合 / 流水线
    • Command Handler、Event Bus、Query Pipeline → 数据从源到结果的映射流水线
  3. 不可变数据
    • Event Sourcing、FaaS/Serverless 数据不可变
  4. 可复用 & 可扩展
    • 高阶函数 + 闭包组合 → 与 OO 的策略模式、装饰器模式相似
  5. 公式化抽象
    Result = F n ∘ F n − 1 ∘ . . . ∘ F 1 ( D Input ) \text{Result} = F_n \circ F_{n-1} \circ ... \circ F_1(D_{\text{Input}}) Result=Fn​∘Fn−1​∘...∘F1​(DInput​)

C++/函数式风格 + Lambda/FaaS模拟 写一个完整可运行示例

1⃣ 数据类型与接口契约

#include<iostream>#include<functional>#include<string>#include<vector>// 请求数据类型structRequest{ std::string type;// "Purchase" / "Search"int payload;};// 响应数据类型structResponse{ std::string type;int result;};

解释

  • Request → 数据不可变,输入源
  • Response → 输出结果
  • 对应 FP 公式:
    D Request → F R Response D_{\text{Request}} \xrightarrow{F} R_{\text{Response}} DRequest​F​RResponse​

2⃣ 函数式闭包模拟 FaaS 函数

using FunctionHandler = std::function<Response(const Request&)>;// Purchase Function FunctionHandler purchaseFunction(int multiplier){// multiplier 捕获上下文 → 闭包封装状态return[multiplier](const Request& req)-> Response {int value = req.payload * multiplier;return{"Purchase", value};};}// Search Function FunctionHandler searchFunction(){return[](const Request& req)-> Response {// 模拟查询数据库返回结果int result = req.payload +100;return{"Search", result};};}// Authentication Service FunctionHandler authService(){return[](const Request& req)-> Response {// 简单验证逻辑bool ok =(req.payload >0);return{"Auth", ok ?1:0};};}

解释

  • 每个服务都是闭包(闭包封装状态 + 单一接口)
  • FunctionHandler → 接口契约,输入 Request 输出 Response
  • 对应 FP 高阶函数思想,函数可动态生成并组合

3⃣ Gateway 路由(函数组合流水线)

Response gateway(const Request& req,const FunctionHandler& auth,const FunctionHandler& purchase,const FunctionHandler& search){// 第一步:认证 Response authRes =auth(req);if(authRes.result ==0){return{"Error",0};}// 第二步:路由到具体功能if(req.type =="Purchase"){returnpurchase(req);}elseif(req.type =="Search"){returnsearch(req);}else{return{"Error",0};}}

解释

  • Gateway → 高阶函数组合,动态路由
  • 数据流类似函数管道:
    F Gateway = F Auth ∘ F Route F_{\text{Gateway}} = F_{\text{Auth}} \circ F_{\text{Route}} FGateway​=FAuth​∘FRoute​

4⃣ 模拟数据库存储(不可变数据/事件式更新)

structDatabase{ std::vector<Response> store;voidwrite(const Response& res){// 不修改原数据,推入新结果 store.push_back(res);}voidprint()const{for(constauto& r : store) std::cout << r.type <<" -> "<< r.result << std::endl;}};

解释

  • 模拟存储 → 不可变数据概念
  • 类似 Event Sourcing,保存状态快照而不是直接修改

5⃣ 完整数据流示例

intmain(){ Database db;// 创建闭包函数服务auto auth =authService();auto purchase =purchaseFunction(2);// multiplier = 2auto search =searchFunction();// 模拟客户端请求 Request req1{"Purchase",50}; Request req2{"Search",25};// Gateway 处理请求 → 返回响应 Response res1 =gateway(req1, auth, purchase, search); Response res2 =gateway(req2, auth, purchase, search);// 写入数据库(不可变) db.write(res1); db.write(res2);// 查询结果 db.print();}

https://godbolt.org/z/W6ohMs5Ex

输出:

Purchase -> 100 Search -> 125 

公式化映射
Result = F Gateway ∘ ( F Auth , F Purchase , F Search ) ( D Request ) \text{Result} = F_{\text{Gateway}} \circ (F_{\text{Auth}}, F_{\text{Purchase}}, F_{\text{Search}})(D_{\text{Request}}) Result=FGateway​∘(FAuth​,FPurchase​,FSearch​)(DRequest​)

  • D Request D_{\text{Request}} DRequest​ → 客户端请求
  • F Gateway F_{\text{Gateway}} FGateway​ → 高阶函数组合
  • F Auth , F Purchase , F Search F_{\text{Auth}}, F_{\text{Purchase}}, F_{\text{Search}} FAuth​,FPurchase​,FSearch​ → 独立闭包函数
  • Result → 数据库/响应

6⃣ 特点总结

  1. 单一接口 + 闭包封装 → 每个服务独立可组合
  2. 高阶函数组合 / 流水线 → Gateway 管道
  3. 不可变数据 → 数据库事件式写入
  4. 函数式架构思想落地 → 类似 FaaS / Serverless
  5. 扩展性 → 可新增功能函数而不修改 Gateway 或已有闭包
    这个例子完整模拟了 Mermaid Serverless/FaaS 架构图的函数式实现

C++中的函数式设计(RxCpp 示例解析)

1⃣ 优点与不足

优点

  • 高度抽象,易于扩展:可以用组合操作符对数据流进行链式处理
  • 声明式表达,易于理解:表达“做什么”,而不是“怎么做”
  • 形式化验证,自证能力强:纯函数和组合可以形式化推理
  • 不可变状态,易于并发:每个操作都返回新数据,不修改源
    不足
  • 代数化建模门槛高:需要将问题转化为函数/流
  • 性能开销:在图灵机上存在额外对象创建和调度开销
  • 不可变约束可能造成数据泥团耦合
  • 闭包接口粒度过细:需要组合才能形成业务概念

2⃣ RxCpp 代码解析

#include<rxcpp/rx.hpp>#include<tuple>#include<thread>#include<cstdio>intmain(){// 创建一个事件循环线程调度器auto threads = rxcpp::observe_on_event_loop();

解释

  • observe_on_event_loop() → 创建一个 线程调度器,用于异步执行数据流
  • 对应函数式并发思想: S → schedule S ′ S \xrightarrow{\text{schedule}} S' Sschedule​S′
// 无限整数流(从1开始)auto values = rxcpp::observable<>::range(1);

解释

  • observable<>::range(1) → 生成 数据流
  • 无限流直到溢出(infinite stream)
  • 数据不可变,每个事件是新值
    公式表示:
    v a l u e s = 1 , 2 , 3 , . . . values = {1, 2, 3, ... } values=1,2,3,...
// 第一个流 s1:在线程上执行,并映射到元组auto s1 = values .subscribe_on(threads)// 指定线程执行.map([](int prime){ std::this_thread::yield();// 主动让出线程return std::make_tuple("1:", prime);// 映射到元组});

解释

  • subscribe_on(threads) → 数据源在事件循环线程上执行
  • map(...) → 高阶函数,映射函数,将整数映射到元组 ("1:", prime)
  • yield() → 模拟异步 / 并发场景
    公式:
    s 1 = v a l u e s → map ( λ p . ( " 1 : " , p ) ) S 1 s1 = values \xrightarrow{\text{map}(\lambda p. ("1:", p))} S_1 s1=valuesmap(λp.("1:",p))​S1​
// 第二个流 s2:类似 s1,但标签为 "2:"auto s2 = values .subscribe_on(threads).map([](int prime){ std::this_thread::yield();return std::make_tuple("2:", prime);});

解释

  • 两个流独立并发执行,闭包捕获上下文
  • 对应 函数式并发组合
    公式:
    s 2 = v a l u e s → map ( λ p . ( " 2 : " , p ) ) S 2 s2 = values \xrightarrow{\text{map}(\lambda p. ("2:", p))} S_2 s2=valuesmap(λp.("2:",p))​S2​
// 合并流、取前6个、在指定线程观察 s1 .merge(s2)// 合并两个流.take(6)// 取前6个元素.observe_on(threads)// 在线程上观察.as_blocking()// 阻塞直到完成.subscribe(rxcpp::util::apply_to([](constchar* s,int p){printf("%s %d\n", s, p);}));}

解释

  1. merge(s2) → 将两个流组合 → 高阶函数组合
    • 类似函数式管道组合: S merged = S 1 ∪ S 2 S_{\text{merged}} = S_1 \cup S_2 Smerged​=S1​∪S2​
  2. take(6) → 取前6个 → 数据流截断
  3. observe_on(threads) → 指定观察线程
  4. as_blocking() → 阻塞等待完成,便于同步打印
  5. subscribe(...) → 消费数据,每个事件执行闭包
    • 闭包捕获上下文(标签 “1:” / “2:”)
      公式:
      R e s u l t = subscribe ( take 6 ( merge ( S 1 , S 2 ) ) ) Result = \text{subscribe}(\text{take}_6(\text{merge}(S_1, S_2))) Result=subscribe(take6​(merge(S1​,S2​)))
      示例输出(可能顺序随机,由线程调度决定):
1: 1 2: 1 1: 2 2: 2 1: 3 2: 3 

3⃣ 函数式设计特征总结

  1. 数据流不可变 → 每个 map/merge 操作返回新流
  2. 闭包 + 高阶函数 → map/subscription 是独立闭包
  3. 并发与组合 → merge/schedule 是组合操作
  4. 声明式表达 → 不关心执行细节,只关注数据流映射
  5. 可形式化验证 → 每个 map/merge/take 操作可抽象公式:
    S o u t = t a k e 6 ( m e r g e ( m a p 1 ( S ) , m a p 2 ( S ) ) ) S_{out} = take_6(merge(map_1(S), map_2(S))) Sout​=take6​(merge(map1​(S),map2​(S)))

多范式融合的 C++ 程序设计策略解析

你提供的 Mermaid 图和描述展示了 多种编程范式的融合:从指令式、过程式、面向对象,到函数式和响应式,核心思想是 通过约束建立规则,通过规则描述系统

1⃣ 指令式 / 过程式控制流(Imperative / Procedural)

goto

goto

goto

Instruction

Block 1

Block 2

Block 3

Block 4

Block 5

Block 6

理解

  • 指令式编程强调 顺序执行 + 控制流
  • goto 模拟 循环和跳转
  • 对应 C++ 示例:
int i =0;while(i <10){// 等价于 goto 循环if(i %2==0){ std::cout << i << std::endl;} i++;}

公式化表示
B i → goto B j B_i \xrightarrow{\text{goto}} B_j Bi​goto​Bj​
即程序状态从一个基本块跳转到另一个基本块。

2⃣ 过程式 / 模块化

Procedure

理解

  • 函数 / 过程模块化:限制指令作用域,分层管理
  • 每个过程/函数都是 单一接口,内部实现封装
  • 对应 C++ 示例:
intsum(int a,int b){return a + b;}intmain(){int result =sum(3,5); std::cout << result << std::endl;}

公式化
R = F ( A , B ) R = F(A, B) R=F(A,B)

  • F F F → 函数接口
  • A , B A,B A,B → 输入参数
  • R R R → 输出结果

3⃣ 面向对象设计(OO)

组合

继承/关联

继承/关联

组合/关联

«Object»

Root

NodeA

NodeB

NodeC

NodeD

理解

  • 面向对象强调 数据与操作封装
  • 支持 继承 / 多态 / 组合
  • 对应 C++ 示例:
classNodeA{public:virtualvoidprocess(){ std::cout <<"NodeA"<< std::endl;}};classNodeB:publicNodeA{public:voidprocess()override{ std::cout <<"NodeB"<< std::endl;}};

公式化表示
O = D , F D = 数据 , F = 方法/函数 O = {D, F} \quad D = \text{数据}, F = \text{方法/函数} O=D,FD=数据,F=方法/函数

4⃣ 函数式 / Lambda / FaaS

λ

λ

λ

λ

理解

  • 函数式设计:约束数据可变性,采用 闭包 + 高阶函数
  • 每个 Lambda 是单一接口,可组合形成复杂逻辑
  • 对应 C++ 示例:
auto multiply =[](int x){return x*2;};auto addOne =[](int x){return x+1;};auto pipeline =[=](int x){returnaddOne(multiply(x));}; std::cout <<pipeline(3)<< std::endl;// 输出 7

公式化表示
f pipeline = a d d O n e ∘ m u l t i p l y f_{\text{pipeline}} = addOne \circ multiply fpipeline​=addOne∘multiply

  • 数据从输入到输出经过函数组合映射

5⃣ 响应式架构(Reactive / RxCpp)

  • 数据流驱动、异步事件、组合操作
  • 可以在 C++ 中通过 RxCpp 表示:
#include<rxcpp/rx.hpp>auto values = rxcpp::observable<>::range(1,10); values .map([](int x){return x*2;})// 函数式映射.filter([](int x){return x%3==0;}).subscribe([](int x){ std::cout << x << std::endl;});

理解

  • 每个操作都是 纯函数 + 高阶函数组合
  • 数据不可变 → 支持并发
  • 响应式数据流 = Lambda + Observable → 事件驱动流水线
    公式:
    S o u t = filter 3 ∘ map ∗ 2 ( S ∗ i n ) S_{out} = \text{filter}_3 \circ \text{map}*2 (S*{in}) Sout​=filter3​∘map∗2(S∗in)

6⃣ 多范式融合策略总结


范式核心约束C++实现特点
指令式指令顺序、GOTOwhile / for / if接近图灵机,灵活
过程式函数封装函数 / 模块分层模块化、可重用
面向对象数据 + 方法封装、继承、多态class / virtual / override封装性强、可扩展
函数式不可变、闭包、组合Lambda / 高阶函数高度抽象、易组合、声明式
响应式数据流、事件驱动RxCpp / Observable并发安全、异步事件组合

设计原则

  1. 左侧范式(指令式 / 过程式) → 灵活、接近硬件
  2. 右侧范式(函数式 / 响应式) → 约束更多,通过规则描述系统
  3. 融合策略 → 分层架构、模块化设计、Lambda/FaaS、事件流组合

7⃣ 公式化抽象总结

数据从输入到输出经过多范式处理:
R = F Reactive ∘ F Lambda ∘ O OO ∘ P Procedure ( D Input ) R = F_{\text{Reactive}} \circ F_{\text{Lambda}} \circ O_{\text{OO}} \circ P_{\text{Procedure}}(D_{\text{Input}}) R=FReactive​∘FLambda​∘OOO​∘PProcedure​(DInput​)

  • D Input D_{\text{Input}} DInput​ → 输入数据
  • P Procedure P_{\text{Procedure}} PProcedure​ → 过程式模块
  • O OO O_{\text{OO}} OOO​ → 面向对象方法调用
  • F Lambda F_{\text{Lambda}} FLambda​ → 函数式闭包 / 高阶函数
  • F Reactive F_{\text{Reactive}} FReactive​ → 响应式组合操作 / 数据流

DCI(Data–Context–Interaction)架构

1⃣ DCI 的基本概念

DCI 是一种 软件架构思想,旨在解决面向对象开发中的“对象贫血”问题,同时更好地表达业务场景和系统行为。

  • D (Data):数据
    • 表示系统的 领域对象(Domain Objects)
    • 只封装属性和最基础的方法,不包含业务逻辑
    • 对应 OO 的实体对象(Entity)
  • C (Context):上下文 / 场景
    • 表示 业务用例或操作场景
    • 将数据对象组合在一起,确定角色和行为
    • 起到“协调者”的作用
  • I (Interaction):交互 / 行为
    • 表示在上下文中数据对象之间的 交互逻辑
    • 可以用函数、闭包、方法来实现
    • 执行时绑定到数据对象
      一句话总结
DCI = 数据 + 场景 + 交互,把系统行为逻辑从数据对象中剥离出来,让对象专注于状态,场景专注于行为组合。

2⃣ 传统 OO 问题 vs DCI

传统 OO:

classUser{public:int balance;voidpurchase(Product p){// 业务逻辑耦合到对象 balance -= p.price;}};

问题

  • 业务逻辑散落在对象中 → “贫血对象”或过于膨胀
  • 难以组合不同角色和不同场景

DCI 思路:

// D: DataclassUser{public:int balance;};classProduct{public:int price;};// C: ContextclassPurchaseContext{public: User& user; Product& product;PurchaseContext(User& u, Product& p):user(u),product(p){}// I: Interaction std::function<void()>checkout(){return[this](){if(user.balance >= product.price) user.balance -= product.price;};}};

特点

  1. 数据对象 UserProduct只存状态
  2. 场景 PurchaseContext负责组合数据对象和交互逻辑
  3. 行为 checkout() → Lambda 闭包,按角色执行
  4. 更清晰表达 用例驱动的业务逻辑

3⃣ DCI 优势

  1. 关注点分离
    • 数据对象只管理状态
    • 上下文管理场景
    • 交互管理行为
  2. 可组合性
    • 不同场景可复用同一数据对象
    • 同一角色逻辑可在多个上下文中复用
  3. 提高业务可理解性
    • 用例流程在 Context 中清晰可读
    • 避免对象逻辑过于分散
  4. 可扩展性
    • 新的角色、新的场景可以独立定义

4⃣ 公式化表示

假设:

  • D = d 1 , d 2 , … D = {d_1, d_2, \dots} D=d1​,d2​,… → 数据对象集合
  • C C C → 上下文函数,组合数据对象和角色
  • I I I → 行为函数,执行交互
    业务用例执行流程:
    Result = I ( C ( D ) ) \text{Result} = I(C(D)) Result=I(C(D))
  • 数据 D D D → 状态
  • 上下文 C C C → 定义角色和组合
  • 行为 I I I → 执行交互逻辑

5⃣ C++ DCI 总结

  • Data → 类 / 对象,封装状态
  • Context → 类 / 函数,组合对象,建立业务场景
  • Interaction → Lambda / 方法,绑定角色并执行操作
    核心思想
“对象是穷人的闭包,闭包是穷人的对象”,DCI 正是把对象、闭包、函数式组合思想融合到业务建模中。

Read more

Python快速落地的临床知识问答与检索项目(2025年9月教学配置部分)

Python快速落地的临床知识问答与检索项目(2025年9月教学配置部分)

项目概述与技术选型 本项目定位为临床辅助决策支持工具,而非替代临床诊断的独立系统,旨在解决医疗行业两大核心痛点:一是医学知识更新速率加快,2025 年临床指南年均更新量较 2020 年增长 47%,传统知识管理方式难以同步;二是科室规范呈现碎片化分布,不同院区、亚专科的诊疗流程存在差异,导致知识检索效率低下。技术路线采用 RAG 知识库 + ChatFlow 多轮对话 + 工具节点对接 的三层架构,通过整合指南文献、临床路径和院内 SOP 文档,满足门诊快速问诊、病房随访问答及科室知识库精准检索需求,最终实现医疗信息可及性提升 30%、基层医生决策效率提高 25% 的核心价值目标[1]。 技术栈选型分析 1. 大语言模型:领域专精与多模态融合 临床知识问答核心模型需兼顾专业性与部署灵活性。2025 年主流选型包括: * Chimed - GPT:基于 Ziya - V2 架构,通过预训练、

By Ne0inhk

股票分析:Python 爬取同花顺股票数据(技术指标提取)

Python 爬取同花顺股票数据及技术指标提取详解(2026 年视角) 在 2026 年,使用 Python 爬取股票数据已成为量化分析、AI 预测和个人投资工具的标配。同花顺(iFinD)作为国内主流金融平台,提供丰富的股票行情、历史 K 线和技术指标数据。但直接爬取其官网网页可能面临反爬机制、数据延迟或法律风险(需遵守平台条款,避免商业滥用)。推荐使用开源库如 Akshare 或 Tushare,这些库本质上是封装好的爬虫接口,支持同花顺等数据源,免费且高效。 本教程基于 2026 年最新实践: * 首选库:Akshare(免费开源,支持实时/历史数据,数据来源包括同花顺、东方财富等)。 * 备选:Tushare(需注册 Token,免费版有限额,付费版更稳定)。 * 技术指标提取:使用 pandas_ta

By Ne0inhk

06 Python 数据分析入门:集中趋势与离散程度

Python 数据分析入门:一文搞懂集中趋势与离散程度(附 Pandas 实战) 适合人群:Python 初学者 / 数据分析入门 / 统计学基础学习者 / 教学案例分享 在做数据分析时,我们经常会遇到这样的问题: * 一组数据的“平均水平”到底是多少? * 为什么两组数据均值差不多,但实际情况完全不同? * 如何判断数据是否稳定,波动大不大? * 数据里有没有异常值? 这些问题,本质上都离不开两个统计学基础概念: * 集中趋势 * 离散程度 本文用一个非常简单的案例——班级成绩分析,带你从 0 到 1 学会这些统计指标,并用 Pandas 完成实战分析。 一、先看一个问题:平均分差不多,班级情况就一样吗? 假设现在有两个班级的数学成绩: A班成绩 =[85,82,88,84,86,83,87,85,

By Ne0inhk
Python + BS4实战:手把手带你爬取商业数据

Python + BS4实战:手把手带你爬取商业数据

目录 一、bs4篇 1.bs4介绍 1.1 什么是BeautifulSoup4? 1.2 为什么选择BeautifulSoup4?       核心优势 2.bs4详解 2.1 首先下载bs4 2.2 接下来引入一个使用bs4的例子让我们快速熟悉它 2.3 运行结果 3.bs4使用实战案例 3.1 完整代码 3.2 为什么会影响翻页 3.3 反爬机制 3.4 已知信息 3.5 解决思路 3.6 结果展示 3.7 容易混淆的一点 3.8 图片爬虫 🌟 Hello,

By Ne0inhk