一、领域驱动设计(DDD)概述

领域驱动设计(Domain Driven Design,简称 DDD)是一种将软件设计与复杂业务领域紧密结合的方法论,核心思想是“模型先行,业务驱动”,通过领域模型(Domain Model)来表达业务规则和逻辑。
DDD 的关键点:

  1. 聚焦核心领域(Core Domain)
    • 不把所有业务都当成平等的模块处理,而是找出业务的核心价值。
    • 核心领域用领域模型(Domain Model)表达,其他部分可用通用子域(Generic Subdomain)或支持子域(Supporting Subdomain)处理。
  2. 协作与统一语言(Ubiquitous Language)
    • 领域模型不仅是代码,更是业务专家和开发者的沟通工具。
    • 所有团队成员使用同一语言(如类名、方法名、事件名)描述业务逻辑。
  3. 迭代式演进(Iterative Evolution)
    • 领域模型不是一次性设计完成,而是随着业务理解不断演化。
    • DDD 鼓励小步迭代、持续重构,减少复杂性。

二、领域模型落地面临的技术挑战

在实际落地 DDD 时,开发团队通常会遇到以下技术挑战:

  1. 模型与实现的偏差
    • 领域模型是业务概念的抽象,而代码实现容易受技术约束(框架、数据库)影响。
    • 解决办法:使用**聚合(Aggregate)**封装核心业务逻辑,保持模型纯净。
  2. 复杂性管理
    • 大型业务系统中,子域多、边界复杂,容易导致模型膨胀或耦合。
    • 解决办法:划分界限上下文(Bounded Context),明确每个上下文负责的领域逻辑。
  3. 持久化与性能矛盾
    • 领域模型对象和数据库表结构不一定完全匹配,尤其在 ORM 映射中。
    • 解决办法:使用 仓储模式(Repository Pattern) 隐藏数据访问细节。
  4. 事件驱动与一致性
    • 跨上下文或微服务时,领域事件(Domain Event)传播与事务一致性难以保证。
    • 解决办法:引入事件总线(Event Bus)和最终一致性(Eventual Consistency)策略。
  5. 技术团队和业务团队协同困难
    • 团队理解领域模型差异大,容易出现模型不统一或功能设计偏差。
    • 解决办法:持续研讨、共享 Ubiquitous Language,并结合原型或行为驱动开发(BDD)。

三、DDD 关键概念与示例代码(Java 风格)

1. 实体(Entity)

实体有唯一标识,生命周期内可以变更属性,但身份不变。

#include <iostream>
#include <vector>
#include <string>
#include <numeric> // std::accumulate
#include <memory>  // std::shared_ptr
// ------------------------
// 值对象 OrderItem
// ------------------------
struct OrderItem {
    std::string name;
    double price;
    int quantity;
    OrderItem(const std::string& n, double p, int q) : name(n), price(p), quantity(q) {}
};
// ------------------------
// 实体 Order
// ------------------------
class Order {
private:
    std::string orderId;               // 唯一标识
    std::string customerName;
    std::vector<OrderItem> items;
public:
    Order(const std::string& id, const std::string& customer) 
        : orderId(id), customerName(customer) {}
    // 添加订单项
    void addItem(const OrderItem& item) {
        items.push_back(item);
    }
    // 计算订单总额
    double calculateTotal() const {
        // 数学公式:
        // $Total = \sum_{i=1}^{n} item.price * item.quantity$
        double total = 0.0;
        for (const auto& item : items) {
            total += item.price * item.quantity;
        }
        return total;
    }
    std::string getId() const { return orderId; }
};

2. 值对象(Value Object)

值对象无唯一标识,完全由属性定义。

#include <string>
// 值对象 Address
class Address {
private:
    std::string street;
    std::string city;
    std::string zipCode;
public:
    Address(const std::string& s, const std::string& c, const std::string& z)
        : street(s), city(c), zipCode(z) {}
    // 值对象相等性比较
    bool operator==(const Address& other) const {
        return street == other.street && city == other.city && zipCode == other.zipCode;
    }
};

3. 聚合与仓储(Aggregate & Repository)

聚合根负责维护内部一致性,仓储负责持久化。

#include <unordered_map>
#include <memory>
#include <stdexcept>
// 仓储接口
class OrderRepository {
public:
    virtual void save(const std::shared_ptr<Order>& order) = 0;
    virtual std::shared_ptr<Order> findById(const std::string& orderId) = 0;
};
// 内存实现
class InMemoryOrderRepository : public OrderRepository {
private:
    std::unordered_map<std::string, std::shared_ptr<Order>> storage;
public:
    void save(const std::shared_ptr<Order>& order) override {
        storage[order->getId()] = order;
    }
    std::shared_ptr<Order> findById(const std::string& orderId) override {
        auto it = storage.find(orderId);
        if (it != storage.end()) return it->second;
        throw std::runtime_error("Order not found");
    }
};

四、领域事件与公式示例

DDD 中常用领域事件(Domain Event)表示系统状态变化:

#include <iostream>
#include <chrono>
#include <ctime>
// 领域事件
class OrderPlacedEvent {
private:
    std::string orderId;
    std::time_t timestamp;
public:
    OrderPlacedEvent(const std::string& id) : orderId(id) {
        timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    }
    void publish() const {
        std::cout << "订单已下单: " << orderId 
                  << " 时间: " << std::ctime(&timestamp);
    }
};

测试示例

int main() {
    // 创建订单
    auto order = std::make_shared<Order>("1001", "张三");
    // 添加订单项
    order->addItem({"苹果", 3.5, 5});
    order->addItem({"香蕉", 2.0, 10});
    // 计算总额
    std::cout << "订单总额: " << order->calculateTotal() << std::endl;
    // 发布领域事件
    OrderPlacedEvent event(order->getId());
    event.publish();
    // 仓储操作
    InMemoryOrderRepository repo;
    repo.save(order);
    auto loadedOrder = repo.findById("1001");
    std::cout << "从仓储读取订单总额: " << loadedOrder->calculateTotal() << std::endl;
    return 0;
}

数学公式示例(订单总额):
Total=∑i=1n(pricei×quantityi) Total = \sum_{i=1}^{n} (price_i \times quantity_i) Total=i=1n(pricei×quantityi)

五、总结

领域驱动设计的核心价值:

  1. 将复杂业务抽象成领域模型
  2. 聚焦核心领域,减少无关复杂性。
  3. 使用迭代式演进不断完善模型。
  4. 通过聚合、仓储、领域事件等技术手段,将模型落地到实际系统中。
    落地 DDD 的技术挑战主要是:
  • 模型与实现偏差
  • 复杂性管理
  • 持久化与性能矛盾
  • 跨上下文一致性
  • 团队协同困难
    通过 界限上下文、聚合、仓储、领域事件 可以有效应对这些挑战。

领域建模示意图

<?xml version="1.0" encoding="UTF-8"?> Domain Expert Analyst Developer Developer Ubiquitous Language Whiteboard discussions Application Code Test Code Specs and documentation

一、SVG 图概述

这张 SVG 图展示了 领域建模(Domain Modeling) 中的核心流程和参与者:

  1. 参与者(Actors)
    • Domain Expert(领域专家):业务知识最丰富,定义核心业务概念。
    • Analyst(分析师):把业务需求成可实现的模型和文档。
    • Developer(开发者):负责把领域模型落地到代码。
  2. 核心概念
    • Ubiquitous Language(通用语言):领域专家、分析师、开发者共用的业务语言,用于减少理解偏差。
    • Whiteboard Discussions(白板讨论):团队协作的具体手段,通过图形和流程讨论业务规则。
  3. 输出产物
    • Application Code(应用代码):实现领域逻辑的生产代码。
    • Test Code(测试代码):保证领域模型逻辑正确性。
    • Specs and Documentation(文档与规格说明):确保团队对模型理解一致。
      图中箭头表示信息流与反馈循环,例如从 领域专家 → 分析师 → 开发者 → 白板讨论 → 应用代码/测试/文档,形成 迭代式模型演进

二、理解与落地注释

  1. Ubiquitous Language 的作用

“通用语言”让团队成员在讨论时使用相同术语。
代码实现中,每个类、方法、事件命名都应反映业务概念,而不是技术实现。
C++ 代码示例:

// 订单实体 Order 表示业务中的“Order”,与领域语言一致
class Order {
private:
    std::string orderId;    // 唯一标识
    std::string customerName;
public:
    Order(const std::string& id, const std::string& customer) 
        : orderId(id), customerName(customer) {}
};
  1. 领域专家到开发者的信息流
  • SVG 图中的箭头表示 知识从领域专家 → 分析师 → 开发者传递
  • 开发者通过白板讨论把需求转化为代码。
    示例注释:
// 白板讨论 -> 决定订单的业务规则
// 例如:Order 总额 = 所有订单项价格 * 数量
double calculateTotal(const std::vector<OrderItem>& items) {
    // 数学公式:
    // $Total = \sum_{i=1}^{n} price_i * quantity_i$
    double total = 0.0;
    for (const auto& item : items)
        total += item.price * item.quantity;
    return total;
}
  1. 输出产物的关系
  • Application Code → 实现业务逻辑
  • Test Code → 验证业务逻辑正确性
  • Documentation → 保证团队理解一致
    这些产物形成闭环,帮助迭代优化领域模型。
// 仓储模式示例,隐藏持久化细节
class OrderRepository {
public:
    virtual void save(const std::shared_ptr<Order>& order) = 0;
    virtual std::shared_ptr<Order> findById(const std::string& orderId) = 0;
};

三、数学公式示例

在领域建模中,业务规则通常可以用公式表达,例如订单总额、折扣计算等。

  1. 订单总额(单行公式):
    Total=∑i=1npricei×quantityiTotal = \sum_{i=1}^{n} price_i \times quantity_iTotal=i=1npricei×quantityi
  2. 折扣后总额(多行公式):
    DiscountedTotal=Total−(Total×DiscountRate) 其中 DiscountRate∈[0,1] \text{DiscountedTotal} = Total - (Total \times DiscountRate) \ \text{其中 } DiscountRate \in [0,1] DiscountedTotal=Total(Total×DiscountRate) 其中 DiscountRate[0,1]
  3. 税费计算
    FinalAmount=Total×(1+TaxRate) \text{FinalAmount} = Total \times (1 + TaxRate) FinalAmount=Total×(1+TaxRate)
    这些公式可以直接映射到代码中:
double discountedTotal(double total, double discountRate) {
    return total - (total * discountRate);  // $DiscountedTotal = Total - Total * DiscountRate$
}
double finalAmount(double total, double taxRate) {
    return total * (1 + taxRate);           // $FinalAmount = Total * (1 + TaxRate)$
}

四、总结

这张 SVG 领域建模图传达了 DDD 落地的核心流程:

  1. 协作和统一语言:领域专家、分析师、开发者共同使用 Ubiquitous Language。
  2. 迭代式演进:通过白板讨论、测试和文档,不断完善领域模型。
  3. 产出闭环:Application Code、Test Code、Documentation,确保模型实现、验证和理解一致。
  4. 公式化业务规则:将核心业务逻辑数学化,便于在代码中实现和验证。

领域模型落地的困境

一、领域模型落地的困境概述

在领域驱动设计(DDD)中,领域模型是业务知识的抽象,但在实际落地到代码时,会遇几个典型问题:

1. 模型不能映射到代码 / 映射过于复杂

  • 问题:如果领域模型抽象得过于理论化,开发者无法将其转化为可执行代码,或者映射关系晦涩难懂,那么:
    • 模型的价值降低,不能保证软件正确性。
    • 团队在实现时容易出错或偏离业务意图。
  • 示例
    假设有一个复杂的订单折扣模型:
    FinalPrice=∑i=1npricei×quantityi×f(discounti) FinalPrice = \sum_{i=1}^{n} price_i \times quantity_i \times f(discount_i) FinalPrice=i=1npricei×quantityi×f(discounti)
    如果在代码中写成一大堆嵌套 if-else 或复杂公式,其他开发者很难理解。
    C++ 可读实现示例(带注释):
struct OrderItem {
    double price;
    int quantity;
    double discountRate; // 折扣率
};
// 使用函数明确模型逻辑
double calculateItemPrice(const OrderItem& item) {
    // $ItemTotal = price * quantity * (1 - discountRate)$
    return item.price * item.quantity * (1 - item.discountRate);
}
double calculateOrderTotal(const std::vector<OrderItem>& items) {
    double total = 0.0;
    for (const auto& item : items)
        total += calculateItemPrice(item); // 保持公式可理解
    return total;
}

通过把公式封装成函数,模型和代码映射清晰,避免晦涩。

2. 编写代码的人不理解或不负责模型

  • 问题:如果开发者认为模型只是“文档”,或者不理解如何让模型在程序中发挥作用:
    • 领域模型无法反映在软件行为上。
    • 软件可能运行正常,但业务规则偏离模型意图。
  • 示例
// 错误示例:直接硬编码业务逻辑,没有体现模型
double calculateTotalHardcoded() {
    return 100.0 + 200.0; // 忽略折扣、数量等模型规则
}
  • 正确做法是 让模型成为代码的一部分
class Order {
private:
    std::vector<OrderItem> items;
public:
    double calculateTotal() const {
        double total = 0.0;
        for (const auto& item : items)
            total += item.price * item.quantity * (1 - item.discountRate); // 体现模型
        return total;
    }
};

3. 代码改变会破坏模型

  • 问题:如果开发人员在重构代码时没有意识到模型与代码的绑定关系:
    • 改变代码可能破坏模型正确性。
    • 模型失去约束力,软件行为偏离业务规则。
  • 示例
// 修改代码但忽略折扣模型
double Order::calculateTotal() const {
    double total = 0.0;
    for (const auto& item : items)
        total += item.price * item.quantity; // 忽略折扣Rate,模型被破坏
    return total;
}
  • 解决方法
    • 重构时保持模型逻辑完整
    • 可以用 测试代码验证模型一致性
void testOrderTotal() {
    Order order;
    order.addItem({100.0, 2, 0.1}); // price=100, qty=2, discount=10%
    double expected = 100.0 * 2 * (1 - 0.1); // $Total = price * quantity * (1 - discount)$
    assert(order.calculateTotal() == expected);
}

使用测试保证模型与代码的一致性,即便重构也不会破坏模型。

四、总结(理解)

领域模型落地的主要困境:

  1. 模型难以映射到代码 → 解决方法:封装业务公式、明确函数和类。
  2. 开发者不理解或不负责模型 → 解决方法:让模型成为代码的一部分,并用统一语言命名。
  3. 重构可能破坏模型 → 解决方法:用测试验证模型一致性,确保代码改动不违背模型逻辑。
    核心原则
  • 领域模型必须可执行 → 不能只是文档。
  • 代码必须忠实实现模型 → 避免偏离业务逻辑。
  • 重构要保持模型完整 → 用测试和约束保证一致性。

“实现领域驱动设计(DDD Implementation)”

一、实现领域模型的原则

  1. 重新阐释和发展 DDD
    • DDD 不仅是理论,更是 模型驱动的软件实现方法
    • 核心目标:让 代码忠实反映业务模型,而不是仅仅是技术实现。
  2. 模型的可执行性
    • 领域模型不是文档,而是 可执行的代码,能直接表达业务规则。
    • 核心思想:模型就是代码,代码就是模型
  3. 案例易于理解
    • 具体业务案例(如订单管理)来演示 DDD 模型和代码的映射。

二、实现模式与 DDD

  • 实现模式介于 设计模式语言手册之间,帮助开发者把领域模型落地。
  • 与设计模式区别:
    • 设计模式关注对象组织和交互,是 高层设计决策
    • 实现模式关注 如何用语言实现模型,是低层可执行实践

比如 “聚合根(Aggregate Root)+ 仓储(Repository)+ 领域事件(Domain Event)”就是 DDD 的经典实现模式。

三、C++ 代码示例:实现领域模型

下面用 订单系统 举例,展示 实体、值对象、聚合、仓储、领域事件 的完整实现。

1. 值对象(Value Object)示例

#include <string>
// 值对象 Address
class Address {
private:
    std::string street;
    std::string city;
    std::string zipCode;
public:
    Address(const std::string& s, const std::string& c, const std::string& z)
        : street(s), city(c), zipCode(z) {}
    // 值对象比较逻辑
    bool operator==(const Address& other) const {
        return street == other.street && city == other.city && zipCode == other.zipCode;
    }
};

说明
值对象无唯一标识,由属性定义,完全不可变,保证模型一致性。

2. 实体(Entity)示例

#include <vector>
#include <memory>
struct OrderItem {
    std::string name;
    double price;
    int quantity;
    double discountRate; // 折扣率
};
// 订单实体 Order
class Order {
private:
    std::string orderId;                 // 唯一标识
    std::string customerName;
    std::vector<OrderItem> items;
public:
    Order(const std::string& id, const std::string& customer)
        : orderId(id), customerName(customer) {}
    void addItem(const OrderItem& item) {
        items.push_back(item);
    }
    // 订单总额计算公式
    // $Total = \sum_{i=1}^{n} price_i * quantity_i * (1 - discount_i)$
    double calculateTotal() const {
        double total = 0.0;
        for (const auto& item : items) {
            total += item.price * item.quantity * (1 - item.discountRate);
        }
        return total;
    }
    std::string getId() const { return orderId; }
};

说明

  • 订单是实体(Entity),有唯一标识 orderId
  • 聚合内部逻辑(如总额计算)直接映射业务模型。

3. 聚合根与仓储(Aggregate & Repository)

#include <unordered_map>
#include <stdexcept>
class OrderRepository {
public:
    virtual void save(const std::shared_ptr<Order>& order) = 0;
    virtual std::shared_ptr<Order> findById(const std::string& orderId) = 0;
};
// 内存仓储实现
class InMemoryOrderRepository : public OrderRepository {
private:
    std::unordered_map<std::string, std::shared_ptr<Order>> storage;
public:
    void save(const std::shared_ptr<Order>& order) override {
        storage[order->getId()] = order;
    }
    std::shared_ptr<Order> findById(const std::string& orderId) override {
        auto it = storage.find(orderId);
        if (it != storage.end()) return it->second;
        throw std::runtime_error("Order not found");
    }
};

说明

  • 仓储模式隐藏持久化细节,让领域模型代码 专注业务逻辑
  • 聚合根(Order)保证内部一致性,外部只能通过聚合根访问。

4. 领域事件(Domain Event)

#include <iostream>
#include <chrono>
#include <ctime>
class OrderPlacedEvent {
private:
    std::string orderId;
    std::time_t timestamp;
public:
    OrderPlacedEvent(const std::string& id) : orderId(id) {
        timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    }
    void publish() const {
        std::cout << "订单已下单: " << orderId 
                  << " 时间: " << std::ctime(&timestamp);
    }
};

说明

  • 领域事件表示业务状态变化,可触发异步操作或集成其他系统。

5. 测试和验证模型一致性

#include <cassert>
void testOrderTotal() {
    Order order("1001", "张三");
    order.addItem({"苹果", 2.0, 5, 0.1});
    order.addItem({"香蕉", 1.0, 10, 0.2});
    // $Total = 2*5*(1-0.1) + 1*10*(1-0.2) = 9 + 8 = 17$
    assert(order.calculateTotal() == 17.0);
}

说明

  • 测试确保 代码行为与领域模型一致
  • 即使重构,测试保证模型不被破坏。

四、总结

  1. 实现领域模型就是 用代码忠实表达业务模型,不要仅依赖文档或 UML。
  2. 实现模式提供可执行实践:聚合根、仓储、领域事件、值对象。
  3. 公式化业务规则方便验证:
    Total=∑i=1npricei×quantityi×(1−discounti) Total = \sum_{i=1}^{n} price_i \times quantity_i \times (1 - discount_i) Total=i=1npricei×quantityi×(1discounti)
  4. 测试是模型落地的保证,确保开发者理解并遵守模型。

C++ 语言面向模型的实现模式

一、面向模型的实现模式概述

C++ 面向模型的实现模式(Model-Oriented Implementation Pattern)核心思想:

  1. 模型驱动实现
    • 代码结构直接反映领域模型的结构与规则。
    • 保证 模型与代码双向映射,即模型可以指导代码实现,代码也能反映模型状态。
  2. 语言层面特性
    • 利用 C++ 的特性:
      • 类和继承 → 表达实体和聚合关系
      • 值对象(immutable) → 使用 conststruct
      • 智能指针 → 管理聚合根生命周期
      • 模板 → 泛化可重用组件
  3. 实现模式组织方式
    • 模型元素类别组织:实体、值对象、聚合、仓储、领域事件、服务。
    • 每类元素都对应 C++ 类型/类/函数,并保持业务规则完整。

二、核心模式及 C++ 实现示例

1. 值对象(Value Object)模式

  • 实现要点
    • 无唯一标识
    • 不可变
    • 重载相等运算符以便比较
#include <string>
struct Address {
    const std::string street;
    const std::string city;
    const std::string zipCode;
    Address(const std::string& s, const std::string& c, const std::string& z)
        : street(s), city(c), zipCode(z) {}
    bool operator==(const Address& other) const {
        return street == other.street && city == other.city && zipCode == other.zipCode;
    }
};

说明

  • const 确保值对象不可变
  • 模型规则直接映射到 C++ 类型

2. 实体(Entity)与聚合根(Aggregate Root)模式

  • 实现要点
    • 实体有唯一标识
    • 聚合根保证内部一致性
    • 封装业务规则
#include <vector>
#include <memory>
#include <string>
// 订单项
struct OrderItem {
    std::string name;
    double price;
    int quantity;
    double discountRate; // 折扣率
};
// 聚合根:Order
class Order {
private:
    std::string orderId;
    std::vector<OrderItem> items;
public:
    Order(const std::string& id) : orderId(id) {}
    void addItem(const OrderItem& item) {
        items.push_back(item);
    }
    double calculateTotal() const {
        // $Total = \sum_{i=1}^{n} price_i * quantity_i * (1 - discount_i)$
        double total = 0.0;
        for (const auto& item : items)
            total += item.price * item.quantity * (1 - item.discountRate);
        return total;
    }
    std::string getId() const { return orderId; }
};

说明

  • 聚合根保证内部状态一致
  • 业务规则(总额计算)直接映射模型公式

3. 仓储(Repository)模式

  • 实现要点
    • 隐藏数据存储
    • 提供聚合根访问接口
#include <unordered_map>
#include <stdexcept>
class OrderRepository {
public:
    virtual void save(const std::shared_ptr<Order>& order) = 0;
    virtual std::shared_ptr<Order> findById(const std::string& orderId) = 0;
};
// 内存仓储实现
class InMemoryOrderRepository : public OrderRepository {
private:
    std::unordered_map<std::string, std::shared_ptr<Order>> storage;
public:
    void save(const std::shared_ptr<Order>& order) override {
        storage[order->getId()] = order;
    }
    std::shared_ptr<Order> findById(const std::string& orderId) override {
        auto it = storage.find(orderId);
        if (it != storage.end()) return it->second;
        throw std::runtime_error("Order not found");
    }
};

说明

  • 仓储是模型到代码的桥梁
  • 聚合内部逻辑完全不依赖持久化实现

4. 领域事件(Domain Event)模式

#include <iostream>
#include <chrono>
#include <ctime>
class OrderPlacedEvent {
private:
    std::string orderId;
    std::time_t timestamp;
public:
    OrderPlacedEvent(const std::string& id) : orderId(id) {
        timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    }
    void publish() const {
        std::cout << "订单已下单: " << orderId 
                  << " 时间: " << std::ctime(&timestamp);
    }
};

说明

  • 模型状态变化可以生成事件
  • 事件封装模型信息,便于异步处理或集成

5. 服务(Domain Service)模式

  • 处理跨聚合逻辑或复杂业务规则
class DiscountService {
public:
    // 计算订单折扣总额
    static double calculateDiscount(const Order& order) {
        double totalDiscount = 0.0;
        for (const auto& item : order.getItems()) {
            totalDiscount += item.price * item.quantity * item.discountRate;
        }
        // $TotalDiscount = \sum_{i=1}^{n} price_i * quantity_i * discount_i$
        return totalDiscount;
    }
};

说明

  • 服务处理模型之外的业务逻辑
  • 保持聚合根单一职责

三、从模型到代码的双向映射最佳实践

  1. 模型公式 → 函数/方法
    • 每个业务公式在 C++ 中有对应函数
    • 示例:总额公式
      Total=∑i=1npricei×quantityi×(1−discounti) Total = \sum_{i=1}^{n} price_i \times quantity_i \times (1 - discount_i) Total=i=1npricei×quantityi×(1discounti)
  2. 模型关系 → 类/聚合
    • 聚合根管理内部实体和值对象
    • 外部只能通过聚合根访问,保证一致性
  3. 模型状态变化 → 领域事件
    • 每次重要业务状态变化产生事件
    • 用事件驱动保持模型与外部系统同步
  4. 模型验证 → 测试代码
    • 单元测试确保模型逻辑正确性
    • 避免重构破坏模型

四、总结(理解)

  • 面向 C++ 的模型实现模式,把 模型映射到可执行代码,保持业务规则清晰。
  • 核心模式:
    • 值对象(Value Object) → 不可变、可比较
    • 实体/聚合根(Entity/Aggregate Root) → 唯一标识、封装业务规则
    • 仓储(Repository) → 隐藏数据存储
    • 领域事件(Domain Event) → 模型状态变化
    • 服务(Domain Service) → 处理跨聚合或复杂业务逻辑
  • 公式映射原则:
    Total=∑i=1npricei×quantityi×(1−discounti) \text{Total} = \sum_{i=1}^{n} price_i \times quantity_i \times (1 - discount_i) Total=i=1npricei×quantityi×(1discounti)
  • 测试确保模型落地与业务一致。

C++面向模型的实现模式

表达概念与关系

生命周期管理

物理设计

表达概念

表达关系

mermaid

flowchart LR
    A[C++面向模型的实现模式] -->| | B{ }
    B --> C[表达概念与关系]
    B --> D[生命周期管理]
    B --> E[物理设计]
    C --> F[表达概念]
    C --> G[表达关系]
    style A fill:#c0392b,stroke:#000,stroke-width:2px,color:#fff,font-weight:bold
    style C fill:#2980b9,stroke:#000,stroke-width:2px,color:#fff
    style D fill:#27ae60,stroke:#000,stroke-width:2px,color:#fff
    style E fill:#7f8c8d,stroke:#000,stroke-width:2px,color:#fff
    style F fill:#fff,stroke:#000
    style G fill:#fff,stroke:#000

C++面向模型的
实现模式

表达概念

表达概念

表达关系

生命周期管理

物理设计

mermaid

mindmap
  root((C++面向模型的<br>实现模式))
    ["表达概念"]
      表达概念
      表达关系
    ["生命周期管理"]
    ["物理设计"]

一、流程图理解

C++面向模型的实现模式

表达概念与关系

生命周期管理

物理设计

表达概念

表达关系

1. 核心节点解析

  1. A: C++ 面向模型的实现模式
    • 核心主题,指 如何在 C++ 中把领域模型转化为可执行代码
  2. B: 模型实现的三个维度
    • 表达概念与关系(C):用 C++ 类、结构体和方法表达业务实体、值对象和聚合关系。
    • 生命周期管理(D):管理对象的创建、销毁、所有权和聚合根的生命周期,C++ 可用智能指针(std::shared_ptr/std::unique_ptr)实现。
    • 物理设计(E):数据库、存储映射和性能优化,保证模型落地后的执行效率。
  3. C → F: 表达概念
    • 用类/结构体表达业务概念,如订单(Order)、客户(Customer)、订单项(OrderItem)。
  4. C → G: 表达关系
    • 表达实体之间的关系,例如聚合根包含子实体,订单包含多个订单项。

二、C++ 面向模型的实现示例

1. 表达概念(F)

struct OrderItem {
    std::string name;
    double price;
    int quantity;
    double discountRate; // 折扣率
};
class Order { // 聚合根
private:
    std::string orderId;
    std::vector<OrderItem> items;
public:
    Order(const std::string& id) : orderId(id) {}
    void addItem(const OrderItem& item) { items.push_back(item); }
    double calculateTotal() const {
        // $Total = \sum_{i=1}^{n} price_i * quantity_i * (1 - discount_i)$
        double total = 0.0;
        for (const auto& item : items)
            total += item.price * item.quantity * (1 - item.discountRate);
        return total;
    }
};

注释

  • OrderOrderItem 对应业务概念
  • calculateTotal 公式直接映射业务规则

2. 表达关系(G)

class Customer {
private:
    std::string customerId;
    std::string name;
    std::vector<std::shared_ptr<Order>> orders; // 订单集合
public:
    void addOrder(const std::shared_ptr<Order>& order) {
        orders.push_back(order);
    }
    double totalSpent() const {
        double total = 0.0;
        for (const auto& order : orders)
            total += order->calculateTotal(); // 订单总额累加
        return total;
    }
};

注释

  • CustomerOrder聚合关系
  • C++ 用 std::shared_ptr 管理生命周期

3. 生命周期管理(D)

#include <memory>
// 聚合根生命周期由智能指针管理
auto order = std::make_shared<Order>("1001");
auto customer = std::make_shared<Customer>();
customer->addOrder(order); // 生命周期安全

说明

  • C++ 通过 std::shared_ptr 保证聚合内部对象不会悬空
  • 生命周期管理是模型落地的关键

4. 物理设计(E)

#include <unordered_map>
class OrderRepository {
private:
    std::unordered_map<std::string, std::shared_ptr<Order>> storage;
public:
    void save(const std::shared_ptr<Order>& order) {
        storage[order->getId()] = order;
    }
    std::shared_ptr<Order> findById(const std::string& id) {
        return storage.at(id);
    }
};

说明

  • 仓储模式隐藏物理存储
  • 使模型代码专注于业务逻辑,而不是数据库操作

三、数学公式在模型中的应用

  1. 订单总额公式
    Total=∑i=1npricei×quantityi×(1−discounti) Total = \sum_{i=1}^{n} price_i \times quantity_i \times (1 - discount_i) Total=i=1npricei×quantityi×(1discounti)
  2. 客户总消费公式
    CustomerTotal=∑j=1mOrderTotalj CustomerTotal = \sum_{j=1}^{m} OrderTotal_j CustomerTotal=j=1mOrderTotalj

在 C++ 中直接通过循环或 std::accumulate 实现。

double total = std::accumulate(customer->orders.begin(), customer->orders.end(), 0.0,
    [](double sum, const std::shared_ptr<Order>& order){ return sum + order->calculateTotal(); });

四、总结

  1. C++ 面向模型的实现模式强调 模型到代码的双向映射
  2. 核心维度
    • 表达概念与关系(C → F/G):类、结构体、聚合关系
    • 生命周期管理(D):智能指针、聚合根管理对象
    • 物理设计(E):仓储模式、数据存储映射
  3. 业务公式在代码中直接实现,保证模型落地的准确性。
  4. 模式组织方式使代码结构清晰、易于维护,并保持业务规则一致性。

Domain Model

Mind

Document

UML

Code

Communication

mermaid

mindmap
  root((Domain Model))
    Mind
    Document
    UML
    Code
    Communication

Domain Model 思维导图 Mermaid 图

一、思维导图理解

Domain Model

Mind

Document

UML

Code

Communication

1. 根节点:Domain Model(领域模型)

  • 领域模型是对业务问题的抽象与概念化,用于指导软件开发。
  • 它是 DDD(领域驱动设计)的核心,直接影响代码设计、文档、沟通方式。

2. 子节点解析

Mind(思维/认知)

  • 模型首先是 概念化思维
  • 团队需要理解业务规则和概念,形成统一认知
  • 关键目标:确保模型表达的概念与业务一致
    示例:用 C++ 类名直接体现业务概念
struct OrderItem {
    std::string name;      // 业务概念:商品名称
    double price;          // 业务概念:单价
    int quantity;          // 业务概念:数量
};

Document(文档)

  • 文档是 模型的文字化记录
  • 用于团队沟通、需求确认、测试基准
    示例:文档描述订单总额公式
    Total=∑i=1npricei×quantityi×(1−discounti) Total = \sum_{i=1}^{n} price_i \times quantity_i \times (1 - discount_i) Total=i=1npricei×quantityi×(1discounti)
  • 可以在 C++ 注释中直接体现公式:
// Order.calculateTotal()
// $Total = \sum_{i=1}^{n} price_i * quantity_i * (1 - discount_i)$
double calculateTotal(const std::vector<OrderItem>& items) {
    double total = 0.0;
    for (const auto& item : items)
        total += item.price * item.quantity * (1 - item.discountRate);
    return total;
}

UML(统一建模语言)

  • UML 是 图形化表达模型的工具
  • 类图、聚合关系图、序列图都是 UML 的典型用法
  • 在 C++ 中可以直接映射 UML 类图到类、聚合、方法
    示例 UML → C++ 映射
Order --- contains ---> OrderItem
OrderItem: name, price, quantity

对应 C++ 代码:

class Order {
private:
    std::vector<OrderItem> items; // 聚合关系
};

Code(代码)

  • 代码是模型的最终落地
  • 模型公式、聚合、实体关系都要在代码中实现
class Order {
public:
    double calculateTotal() const {
        double total = 0.0;
        for (const auto& item : items)
            total += item.price * item.quantity * (1 - item.discountRate);
        return total;
    }
};

Communication(沟通)

  • 沟通保证模型 被团队理解并一致执行
  • 可以通过:
    • 白板讨论(Whiteboard Discussion)
    • UML 图、文档
    • 代码审查和单元测试
      示例:团队沟通业务规则
// 确认业务:每个订单项折扣率必须在 [0, 1]
assert(item.discountRate >= 0.0 && item.discountRate <= 1.0);

二、总结:Domain Model 的五个层面


层面 理解 C++ 实现
Mind 概念化思维,理解业务概念 类、结构体、枚举
Document 文档记录公式和规则 注释 + 文档
UML 图形化表达概念与关系 类图 → C++ 类、聚合
Code 模型落地 聚合根、实体、方法、公式实现
Communication 团队理解和一致性 白板、审查、断言/测试

核心原则

  • 公式映射保证业务逻辑清晰:
    Total=∑i=1npricei×quantityi×(1−discounti) Total = \sum_{i=1}^{n} price_i \times quantity_i \times (1 - discount_i) Total=i=1npricei×quantityi×(1discounti)
  • 聚合关系和实体映射模型结构
  • 文档 + UML + 代码 + 沟通形成闭环,保证模型落地不偏离业务意图

C++ 实现领域模型的“表达概念”模式

一、概念解析:表达概念(Express Concept)

在 C++ 面向模型的实现模式中,表达概念是指如何将领域模型中的业务概念准确地映射到代码中,使代码不仅可执行,同时清晰地反映业务意义。

1. 命名(Naming)

  • 类、方法、属性名称要兼顾:
    • 领域语义:反映业务概念
      • 例如 PortSwitchConnection 等名称清楚表示网络领域概念
    • 模型角色:说明在模型中的职责
      • Port 是聚合根还是实体?属性 bandwidth 是值对象?

2. 实现选型(Implementation Choice)

  • 类(class) → 优先用于复杂业务实体
  • POD(Plain Old Data)结构体 → 用于简单值对象
  • 基本类型 → 用于属性或小型值对象
    示例
struct Bandwidth { // 值对象
    double value;  // 带宽值
    std::string unit; // 单位 Mbps/Gbps
};

3. 构造与析构(Constructor & Destructor)

  • 构造函数服务于 生命周期管理
  • 声明依赖关系,如 Port 依赖 Switch
class Switch; // 前向声明
class Port {
private:
    int id;
    Switch* parentSwitch; // Port 依赖的 Switch
public:
    Port(int id, Switch* s) : id(id), parentSwitch(s) {}
    ~Port() { releaseAllConnections(); }
    void releaseAllConnections() {
        // 清理逻辑,释放依赖资源
    }
};

4. 行为(Behavior)

  • 对外接口(public method)先于对内复用
  • 对外接口体现业务动作,例如端口连接、释放连接
class Connection {};
class PeerNode {}; // 对等节点
class Port {
public:
    Connection connect(PeerNode* peer) {
        // 建立连接逻辑
        Connection conn;
        return conn;
    }
    void release(const Connection& conn) {
        // 断开连接逻辑
    }
};

5. 属性(Attribute)

  • 区分“我是谁”(Identity)
  • 体现依赖关系(Parent、聚合等)
class Port {
private:
    int id;             // 我是谁,唯一标识
    double bandwidth;   // 属性
    Switch* parent;     // 所属聚合
};

6. 物理结构(File & Organization)

  • 每个概念单独文件
  • 命名一致,格式统一
src/
 └─ domain/
     ├─ Port.h
     ├─ Port.cpp
     ├─ Switch.h
     └─ Switch.cpp
  • 便于理解模型角色,保持代码可维护性

二、Mermaid 类图解析

Port

- id : PortId

- bandwidth : Bandwidth

+ Port(id : PortId, s : Switch)

+ connect(peer : PeerNode) : : Connection

+ release(conn : Connection)

解析


成员 角色 理解
id : PortId 私有属性 唯一标识,体现“我是谁”
bandwidth : Bandwidth 私有属性 值对象,表达带宽概念
Port(id, s) 构造函数 声明依赖关系(Port 属于 Switch)
connect(peer) 公共方法 对外行为,建立连接
release(conn) 公共方法 对外行为,释放连接

三、公式化示例(Bandwidth计算)

假设端口带宽计算公式:
EffectiveBandwidth=bandwidth×(1−utilization) EffectiveBandwidth = bandwidth \times (1 - utilization) EffectiveBandwidth=bandwidth×(1utilization)
在 C++ 中:

class Port {
private:
    double bandwidth;    // Mbps
    double utilization;  // 0.0 ~ 1.0
public:
    double effectiveBandwidth() const {
        // $EffectiveBandwidth = bandwidth * (1 - utilization)$
        return bandwidth * (1 - utilization);
    }
};

四、总结:表达概念的实现模式要点

  1. 命名 → 清晰表达领域概念和模型角色
  2. 实现选型 → 类 > POD > 基本类型
  3. 构造与析构 → 管理生命周期,声明依赖关系
  4. 行为 → 公共接口优先体现业务动作
  5. 属性 → 表示身份和依赖
  6. 物理结构 → 文件独立、命名统一

通过这种模式,C++ 代码直接反映领域模型概念,便于维护和理解,同时保证业务规则和对象关系在代码中清晰表达。

C++ 领域模型“表达概念:命名”类图

一、命名原则解析

在领域模型实现中,命名需要兼顾 清晰简洁、领域语义、模型角色

  1. 领域语义(Business Meaning)
    • 类名能直接反映业务概念
    • 例子:
      • Port → 网络端口
      • PortId → 端口唯一标识
      • Bandwidth → 带宽属性
      • Switch → 交换机
  2. 模型角色(Model Role)
    • 通过后缀或命名约定体现角色
    • 常见模式:
      • xxService → 提供业务操作,例如 ConnectionSetupService
      • xxEvent → 领域事件,例如 ConnectionSetupEvent
      • xxFactory → 工厂模式创建实体,例如 SwitchFactory
      • xxRepository → 仓储模式管理聚合,例如 SwitchRepository
      • xxContext → 上下文对象,例如 Context
      • xxRole → 角色对象或接口(本例未体现)

命名原则核心:看到类名即理解业务概念 + 模型角色,无需额外查阅说明文档。

二、类图结构解析

create

create

1 .. *
1 .. N

ContextInitService

Context

ConnectionSetupService

ConnectionSetupEvent

SwitchRepository

SwitchFactory

Switch

Port

PortId

Bandwidth

关系理解

2. 类之间的关系解析


类关系 理解 C++ 对应实现
ContextInitService ..> Context : create 初始化服务创建上下文 构造函数或工厂方法返回 Context 对象
ContextInitService --> SwitchRepository 依赖 Switch 仓储 ContextInitService 持有 SwitchRepository*
ContextInitService --> SwitchFactory 依赖工厂 ContextInitService 调用 SwitchFactory->create()
ConnectionSetupService o-- Context 聚合 Context 持有 Context& 或智能指针
ConnectionSetupService --> ConnectionSetupEvent 生成事件 调用事件构造函数
ConnectionSetupService --> Port 操作端口 调用 Port 方法
SwitchFactory ..> Switch : create 工厂创建 Switch SwitchFactory::createSwitch() 返回 Switch 对象
SwitchRepository o-- "1 .. *" Switch 仓储聚合多个 Switch std::vector<std::shared_ptr<Switch>>
Switch o-- "1 .. N" Port Switch 包含多个 Port 聚合关系,成员变量 std::vector<Port>
Port o-- PortId Port 拥有唯一标识 PortId 值对象成员
Port o-- Bandwidth Port 拥有带宽属性 Bandwidth 值对象成员

类关系 解析
ContextInitService ..> Context : create 初始化服务创建上下文对象
ContextInitService --> SwitchRepository 初始化服务依赖 Switch 仓储
ContextInitService --> SwitchFactory 初始化服务依赖 Switch 工厂
ConnectionSetupService o-- Context 连接建立服务聚合 Context
ConnectionSetupService --> ConnectionSetupEvent 连接建立时生成事件
ConnectionSetupService --> Port 连接建立服务操作 Port 实体
SwitchFactory ..> Switch : create 工厂创建 Switch 实体
SwitchRepository o-- "1 .. *" Switch 仓储聚合多个 Switch
Switch o-- "1 .. N" Port Switch 聚合多个 Port
Port o-- PortId Port 拥有唯一标识
Port o-- Bandwidth Port 拥有带宽属性

三、C++ 实现示例

1. 值对象:PortId 和 Bandwidth

struct PortId {
    int id; // 唯一标识
    PortId(int i) : id(i) {}
};
struct Bandwidth {
    double value; // Mbps
    Bandwidth(double v) : value(v) {}
};

2. 实体:Port

class Port {
private:
    PortId portId;       // 身份标识
    Bandwidth bandwidth; // 带宽属性
public:
    Port(const PortId& id, const Bandwidth& bw) 
        : portId(id), bandwidth(bw) {}
    double effectiveBandwidth(double utilization) const {
        // 公式:有效带宽 = 总带宽 * (1 - 利用率)
        // $EffectiveBandwidth = Bandwidth \times (1 - Utilization)$
        return bandwidth.value * (1 - utilization);
    }
};

3. 聚合:Switch

#include <vector>
#include <memory>
class Switch {
private:
    std::vector<std::shared_ptr<Port>> ports; // 聚合多个 Port
public:
    void addPort(const std::shared_ptr<Port>& port) {
        ports.push_back(port);
    }
    size_t portCount() const { return ports.size(); }
};

4. 仓储与工厂

#include <unordered_map>
#include <string>
class SwitchRepository {
private:
    std::unordered_map<std::string, std::shared_ptr<Switch>> storage;
public:
    void save(const std::string& id, std::shared_ptr<Switch> sw) {
        storage[id] = sw;
    }
    std::shared_ptr<Switch> findById(const std::string& id) {
        return storage.at(id);
    }
};
class SwitchFactory {
public:
    std::shared_ptr<Switch> createSwitch() {
        return std::make_shared<Switch>();
    }
};

5. 服务:ContextInitService & ConnectionSetupService

class Context {};
class ContextInitService {
private:
    SwitchRepository* repo;
    SwitchFactory* factory;
public:
    ContextInitService(SwitchRepository* r, SwitchFactory* f) 
        : repo(r), factory(f) {}
    std::shared_ptr<Context> createContext() {
        auto ctx = std::make_shared<Context>();
        auto sw = factory->createSwitch();
        repo->save("SW1", sw);
        return ctx;
    }
};
class ConnectionSetupEvent {};
class ConnectionSetupService {
private:
    std::shared_ptr<Context> context;
public:
    ConnectionSetupService(std::shared_ptr<Context> ctx) : context(ctx) {}
    void setupConnection(Port& port) {
        ConnectionSetupEvent event; // 生成领域事件
        // 连接建立逻辑
    }
};

四、命名与模型角色总结


类名 领域语义 模型角色
Port 网络端口 实体
PortId 端口唯一标识 值对象
Bandwidth 带宽属性 值对象
Switch 交换机 聚合根
SwitchFactory 创建交换机 工厂
SwitchRepository 保存与查询交换机 仓储
Context 上下文 聚合对象
ContextInitService 初始化上下文 服务
ConnectionSetupService 建立端口连接 服务
ConnectionSetupEvent 连接事件 事件

命名原则:清晰、简洁、直接表达业务语义 + 模型角色,让团队无需额外说明就能理解对象职责。

五、公式映射示例

  1. 端口有效带宽公式
    EffectiveBandwidth=Bandwidth×(1−Utilization) EffectiveBandwidth = Bandwidth \times (1 - Utilization) EffectiveBandwidth=Bandwidth×(1Utilization)
  2. 交换机总有效带宽
    TotalSwitchBandwidth=∑i=1NEffectiveBandwidthi TotalSwitchBandwidth = \sum_{i=1}^{N} EffectiveBandwidth_i TotalSwitchBandwidth=i=1NEffectiveBandwidthi
    在 C++ 中通过方法实现:
double totalBandwidth() const {
    double total = 0.0;
    for (const auto& port : ports)
        total += port->effectiveBandwidth(0.1); // 假设利用率 10%
    return total;
}

总结

  • 类名表达 领域语义(Port、Switch、Bandwidth)
  • 后缀表达 模型角色(Service、Event、Factory、Repository、Context)
  • 代码结构反映领域聚合关系、生命周期管理和业务公式
  • 保证模型落地的清晰性、可维护性和业务一致性

C++ 领域模型“Port、PortId、Bandwidth”类图与实现片段

一、类图理解

Port

PortId

Bandwidth

解析

  • Port
    • 领域实体,表示网络端口
    • 聚合了 PortId(身份标识)和 Bandwidth(带宽属性)
  • PortId
    • 值对象,表示端口的唯一标识
    • 在实现中用 using PortId = int; 简化
  • Bandwidth
    • 值对象,表示端口带宽
    • 封装带宽值,并提供相关方法(如扩展因子 getExtendFactor()
    • 聚合在 Port 中

聚合关系用 o-- 表示,Port 拥有 PortId 和 Bandwidth。

二、PortId.h 实现

using PortId = int; // 端口唯一标识,值对象类型别名

注释

  • 轻量值对象,直接用基本类型 int 表示
  • 简洁且符合领域语义
  • 不包含行为,仅用于身份识别

三、Bandwidth.h 实现

#include "cub/base/EqHelper.h" // 辅助比较等号操作
class Bandwidth {
public:
    explicit Bandwidth(unsigned int value)
        : value(value) {
        // 构造函数,初始化带宽值
    }
    DECL_EQUALS(Bandwidth); // 宏定义,生成 == 和 != 运算符,便于值对象比较
    int getExtendFactor() const {
        // 返回扩展因子(可能用于容量计算或加权带宽)
        return value / 10; // 示例:每10 Mbps为一个单位
    }
private:
    const unsigned int value; // 带宽值,单位 Mbps,常量保证不可修改
};

理解

  1. 构造函数
    • explicit 防止隐式类型转换
    • value 是带宽值
  2. 比较操作
    • DECL_EQUALS(Bandwidth) 宏生成等值操作,保证值对象语义
    • 值对象通过值判断相等,而不是通过指针/地址
  3. 方法 getExtendFactor()
    • 提供行为接口,计算扩展因子
    • 公式示例:
      ExtendFactor=Bandwidth10 ExtendFactor = \frac{Bandwidth}{10} ExtendFactor=10Bandwidth
  4. 私有属性
    • const unsigned int value 确保不可修改,实现不可变对象(Immutable Object)
    • 遵循领域驱动设计中值对象“不可变”的原则

四、Port 实体示例(聚合 PortId 和 Bandwidth)

#include <memory>
#include "PortId.h"
#include "Bandwidth.h"
class Port {
private:
    PortId portId;           // 身份标识
    Bandwidth bandwidth;     // 带宽属性
public:
    Port(PortId id, const Bandwidth& bw)
        : portId(id), bandwidth(bw) {}
    PortId getId() const { return portId; }
    double getEffectiveBandwidth(double utilization) const {
        // $EffectiveBandwidth = Bandwidth \times (1 - Utilization)$
        return bandwidth.getExtendFactor() * (1 - utilization);
    }
};

理解

  1. 聚合关系
    • Port 聚合 PortId 和 Bandwidth
    • 表示实体拥有唯一标识和带宽属性
  2. 方法 getEffectiveBandwidth
    • 根据利用率计算有效带宽
    • 将值对象行为与实体行为组合
  3. 不可变性与封装
    • Bandwidth 内部值不可修改
    • Port 的核心属性通过构造函数注入

五、总结


类型 解释
Port 实体 网络端口,聚合 PortId 和 Bandwidth
PortId 值对象 端口唯一标识,轻量型别别名 int
Bandwidth 值对象 带宽值,封装不可变属性和行为接口

实现原则

  1. 命名清晰简洁:Port、PortId、Bandwidth 一眼能看出领域语义
  2. 聚合关系明确:Port 聚合 PortId 和 Bandwidth
  3. 值对象不可变:Bandwidth 内部值用 const 保证
  4. 行为接口映射业务公式:如有效带宽计算公式
    公式示例
  5. 带宽扩展因子
    ExtendFactor=Bandwidth10 ExtendFactor = \frac{Bandwidth}{10} ExtendFactor=10Bandwidth
  6. 有效带宽
    EffectiveBandwidth=ExtendFactor×(1−Utilization) EffectiveBandwidth = ExtendFactor \times (1 - Utilization) EffectiveBandwidth=ExtendFactor×(1Utilization)
    在 C++ 中对应 getExtendFactor()getEffectiveBandwidth() 方法实现。

C++ 领域模型“表达概念:构造与析构”

一、类图解析

N
1

Switch

Port

PortId

Bandwidth

理解

  1. Switch 与 Port
    • (Port) 聚合多个端口 交换机 (Switch)
    • 1 对 N 聚合关系
    • 聚合意味着 Port 的生命周期与 Switch 相关
  2. Port 与属性
    • Port 聚合 PortIdBandwidth
    • PortId 表示端口身份
    • Bandwidth 表示带宽属性
  3. 生命周期和依赖
    • 构造函数初始化端口时必须提供 Switch 的引用
    • 避免生成无效状态对象

二、Port.h 解析与理解

#include "PortId.h"
#include "Bandwidth.h"
class Switch;
class PeerNode;
class Connection;
class Port {
public:
    // 构造函数:初始化端口并声明对 Switch 的依赖
    Port(PortId id, const Switch& s);
    // 对外行为:建立连接
    Connection* connect(const PeerNode& peer);
    // 对外行为:释放连接
    void release(const Connection* conn);
private:
    // 内部方法:计算带宽扩展因子
    void getBandExpandFactor() const;
private:
    const Switch& switch_; // 声明生命周期依赖,端口绑定到 Switch
    PortId id;             // 端口唯一标识
    Bandwidth bandwidth;   // 带宽属性
};

1. 构造函数(Constructor)

  • Port(PortId id, const Switch& s)
  • 作用
    1. 完整初始化端口对象
    2. 声明端口依赖 Switch(通过引用传递,表明端口生命周期不超过 Switch)
    3. 避免生成状态无意义的对象(对象创建即合法状态)

关键点:

  • 使用 const Switch& s
    • 表示 引用参数,端口对象不拥有 Switch
    • 生命周期早于端口,保证端口操作始终有效
  • 使用 PortId
    • 唯一标识端口
    • 值对象类型,直接存储
  • 使用 Bandwidth
    • 封装带宽值和扩展方法
    • 保持不可变性(const)

2. 析构函数(Destructor)

  • 示例中未显式写析构函数,但可以隐式使用默认析构
  • 对于资源管理(如 Connection*):
    • 可以在 release() 或智能指针中管理内存
    • 避免裸指针泄漏

3. 对外依赖和读写约束


成员 理解 生命周期/约束
const Switch& switch_ 端口依赖的交换机 生命周期依赖外部对象,使用引用保证端口不可独立于 Switch 存在
PortId id 唯一标识 值对象,创建即固定
Bandwidth bandwidth 带宽属性 值对象,封装行为和不可变值
connect(const PeerNode& peer) 建立连接 参数为引用,生命周期早于端口
release(const Connection* conn) 释放连接 通过指针管理资源,释放外部对象

4. 内部方法示例

void Port::getBandExpandFactor() const {
    // 计算带宽扩展因子
    // 公式:$ExtendFactor = Bandwidth / 10$
    int factor = bandwidth.getValue() / 10; 
}
  • 内部方法服务于业务行为,不对外暴露
  • 使用 const 修饰,保证不会修改 Port 状态

三、生命周期管理总结

  1. 构造即完整初始化
    • Port 创建时必须提供 Switch 引用和 PortId
    • 保证对象一开始就有效
  2. 引用声明依赖
    • const Switch& 表示端口依赖交换机
    • 生命周期由外部对象管理
  3. 不可变值对象
    • PortId 和 Bandwidth 保持不可变性
    • 保证业务状态一致
  4. 行为接口先于内部复用
    • 对外方法如 connectrelease 提供业务行为
    • 内部方法如 getBandExpandFactor 服务于实现

四、公式示例

  1. 带宽扩展因子
    ExtendFactor=Bandwidth10 ExtendFactor = \frac{Bandwidth}{10} ExtendFactor=10Bandwidth
  2. 有效带宽
    EffectiveBandwidth=Bandwidth×(1−Utilization) EffectiveBandwidth = Bandwidth \times (1 - Utilization) EffectiveBandwidth=Bandwidth×(1Utilization)

这些公式可在 Port 内部方法或对外接口中实现,保持领域模型与代码一致。

五、C++ 生命周期实践要点


原则 C++ 实现
构造完整 构造函数必须初始化所有成员变量
生命周期依赖 使用引用或智能指针声明依赖关系
不可变值对象 PortId, Bandwidth 使用 const 或值对象保证不可变
对外行为接口 connect/release 提供业务操作
内部方法 getBandExpandFactor 提供辅助行为,不破坏对象状态

总结

  • 构造函数与析构函数服务于 对象生命周期管理
  • 显式依赖声明 (const Switch&) 确保对象状态有效
  • 值对象保持不可变,内部方法服务于行为
  • 代码结构与领域模型紧密映射

UML 五种关系:谁对谁


UML 关系 UML符号 方向理解 谁整体谁部分 口诀 C++ 对应
依赖 ..>(虚线箭头) 箭头从依赖方指向被依赖方 无整体/部分 谁用谁,箭头指向被用对象 方法参数或返回值依赖的对象
关联 -->(实线箭头) 箭头从知道方指向被知道方 无整体/部分 谁知道谁,箭头指向被知道对象 成员指针、引用或智能指针
聚合 o--(空心菱形) 菱形在整体端,箭头指向部分 菱形端是整体,另一端是部分 菱形在哪,哪是整体,箭头指向部分,部分可独立 成员对象/成员指针,生命周期可独立
组合 *--(实心菱形) 菱形在整体端,箭头指向部分 菱形端是整体,另一端是部分 实心菱形整体掌控,部分依赖整体销毁 成员对象或智能指针,随整体销毁
继承 `< –` 箭头从子类指向父类 父类是整体,子类是特化/扩展 父类整体,子类继承 class Child : public Parent

箭头指向的是被动的对象


C++ 领域模型“表达概念:行为”

一、UML 类图分析

«interface»

EventHandler

ConnectionSetupService

Context

理解

  1. EventHandler
    • 声明一个接口(纯虚类),定义事件处理的行为
    • 仅提供对外 API,不包含实现细节
  2. ConnectionSetupService
    • 实现 EventHandler 接口
    • 聚合 Context,对 Context 有依赖,但生命周期独立(空心菱形 o--
    • 对外提供业务相关行为接口,内部逻辑 private
  3. Context
    • 聚合到 ConnectionSetupService
    • 提供服务或状态信息

行为表达原则

  • 最小暴露:尽量少的 public 方法
  • 只读方法:使用 const 修饰,遵循 CQRS
  • 领域含义接口:避免无意义的 get/set(好莱坞原则:别叫我,我来调用你)
  • 接口隔离:通过纯接口暴露 API,子类覆写的虚函数可私有化

二、C++ 代码解析

1. 接口定义(EventHandler.h)

// ConnectionSetupEvent.h
DEF_INTERFACE(EventHandler) 
{
    ABSTRACT(Status handle(const ConnectionSetupEvent& event) const);
};
  • DEF_INTERFACEABSTRACT 宏封装纯虚类定义
  • handle 方法:
    • 接受事件参数 ConnectionSetupEvent
    • 使用 const 修饰,保证方法只读,不修改对象状态
    • 返回状态 Status,可用于 CQRS 的命令与查询分离

2. 服务实现(ConnectionSetupService.h)

#include "EventHandler.h"
class Context;
class ConnectionSetupService : public EventHandler
{
public:
    // 构造函数:注入 Context,声明依赖但不拥有
    ConnectionSetupService(const Context& ctxt)
        : ctxt(ctxt) {}
private:
    // 覆写接口方法,private化
    OVERRIDE(Status handle(const ConnectionSetupEvent& event) const);
private:
    const Context& ctxt; // 聚合 Context,生命周期独立
};
理解与注释
  1. 构造函数
    • 注入 const Context& ctxt
    • 表明生命周期依赖 Context,但不拥有(符合聚合语义)
  2. handle 方法
    • 覆写接口方法,private 化
    • 对外 API 通过 EventHandler 指针或引用访问
    • 避免直接调用,实现好莱坞原则
  3. 成员变量
    • const Context& ctxt
    • 只读引用,遵循 CQRS(命令查询分离)
    • 明确依赖,聚合关系

三、领域行为设计原则对应


原则 C++ 实现 理解
最小暴露 handle 方法 private 尽量少的 public 方法,只暴露必要接口
CQRS const 修饰 handle 查询方法不修改状态,命令方法单独设计
好莱坞原则 接口访问 API 对外调用通过接口,不直接依赖具体实现
接口隔离 EventHandler 纯接口 客户只依赖所需接口,子类实现可私有化方法
聚合 const Context& ctxt 声明依赖 Context,生命周期独立(空心菱形 o–)

四、示例调用模式

const Context ctxt;
ConnectionSetupService service(ctxt);
// 对外调用通过接口
EventHandler* handler = &service;
Status s = handler->handle(event); // 调用私有化实现
  • 外部只通过接口访问
  • 内部实现私有化,最小暴露

六、总结

  1. 行为优先接口化
    • 对外只暴露有意义的业务行为
    • 避免 get/set,突出领域语义
  2. 聚合 Context
    • 声明依赖,但不拥有
    • 生命周期独立 → 空心菱形 o--
  3. 接口继承
    • 接口隔离原则:客户只依赖接口
    • 子类覆写虚函数可私有化,遵循好莱坞原则
  4. CQRS
    • const 修饰只读方法
    • 命令和查询分离

C++ 领域模型“表达概念:属性”

一、属性设计的核心思想

  1. 区分属性与依赖
    • 属性(Attribute / Value):描述对象本身特性,例如 Port 的 id、Bandwidth。
    • 依赖(Dependency / Reference):表示对象对外部实体的引用或关系,例如 Port 对 Switch 的依赖。
  2. 实体标识 vs 值对象
    • 实体(Entity):具有唯一标识,“我是谁”,标识用 id 表示。
      • 示例:PortId id;
    • 值对象(Value Object):无独立标识,值相同则相等,通常不可变。
      • 示例:Bandwidth bandwidth;
  3. 不可变设计
    • 值对象尽量用 const 修饰,避免修改,保证值语义。
    • 比较方法(operator==)实现值比较,支持“我是谁”判断。
  4. 显示化依赖
    • 值类型 → 完全拥有
    • 指针/引用 → 依赖外部对象
    • const 修饰 → 只读访问

二、Port.h 分析

#include "PortId.h"
#include "Bandwidth.h"
class Switch;
class Port {
public:
    // 构造函数:声明依赖 Switch,初始化属性
    Port(PortId id, const Switch& s);
private:
    PortId id;             // 实体标识,区分不同 Port
    Bandwidth bandwidth;   // 值对象,表示带宽属性,不可变
    const Switch& host;    // 显示依赖,端口绑定到 Switch,但生命周期独立
};

理解

  • id → 实体标识,表达“我是谁”,Port 的唯一标识
  • bandwidth → 值对象,保证不可变,通过 operator== 比较值相等性
  • host → 引用外部 Switch,显示依赖关系,const 修饰表示只读

三、Bandwidth.h 分析

class Bandwidth {
public:
    // 显式构造,初始化值对象
    explicit Bandwidth(unsigned int value);
    // 值对象比较方法
    bool operator==(const Bandwidth& other) const;
private:
    const unsigned int value;        // 值对象核心属性
    const unsigned int expandFactor; // 值对象附加属性,不可修改
};

理解

  • 不可变类valueexpandFactor 都是 const
  • 值比较operator==,用于判断值对象是否相等
  • 表达值语义:没有独立标识,完全由属性值决定
  • 扩展性:可以增加其他只读属性,但不改变对象状态

四、属性与依赖映射规则


C++ 成员类型 UML 表达 含义 生命周期语义
值类型(如 Bandwidth) 聚合 o-- 属性/值对象 对象完全拥有,不依赖外部
引用类型(如 const Switch&) 聚合/依赖 显示依赖 生命周期依赖外部,但只读
指针类型 聚合/依赖 显示依赖,可修改 依赖外部对象,可能为空
const 成员 N/A 只读 不可修改,保持不可变性

五、UML 类图示例

Port

-PortId id

-Bandwidth bandwidth

-const Switch& host

PortId

Bandwidth

Switch

  • 空心菱形 o-- → 值对象聚合 方块所在的地方是整体 箭头指向的是被动的对象,比如被依赖的对象,被继承的对象,被包含的对象。
  • 箭头 o--> → 显示依赖 Switch ->箭头指向的是被依赖的对象
  • const 引用 → 只读访问,生命周期独立

六、设计原则总结

  1. 区分属性与依赖
    • 属性 → 值对象或基本类型
    • 依赖 → 指针或引用
  2. 实体标识
    • 用 id 表达唯一性 → PortId id
    • 便于比较和哈希
  3. 值对象不可变
    • 所有属性 const
    • 提供比较方法 operator==
  4. 显示依赖
    • 引用或指针类型
    • const 修饰只读
    • 生命周期外部管理
      总结
  • Port 的属性分为 实体标识 (id)值对象 (bandwidth)依赖 (host)
  • 值对象不可变,提供比较方法
  • 依赖显式化,const 修饰只读
  • UML 聚合/依赖箭头对应 C++ 类型和生命周期语义

C++ 实现领域模型时“表达关系”

一、表达关系的核心概念

在领域建模中,类与类之间的关系主要有以下几类:

关系类型 UML 符号 含义 C++ 映射 生命周期/依赖特点
is-a `< –` 子类化/接口继承 class Child : public Parent 子类对象包含父类部分,继承父类行为
has-a (组合/聚合) *--/o-- 整体-部分关系 成员对象或指针/引用 组合:部分生命周期依赖整体;聚合:部分可独立
has-many 集合 + 聚合/组合 多个部分对象 std::vector<Part>std::list<Part*> 生命周期可依赖或独立,通常通过 Repository 管理
dependency ..> 临时使用/方法参数/调用 方法参数引用或指针 不影响对象存在性,生命周期独立

二、C++ 表达关系示例

1⃣ is-a(继承)

class EventHandler {
public:
    virtual Status handle(const Event& e) const = 0;
    virtual ~EventHandler() = default;
};
// ConnectionSetupService 是 EventHandler 的实现
class ConnectionSetupService : public EventHandler {
public:
    Status handle(const Event& e) const override;
};
  • 概念:ConnectionSetupService 是 EventHandler → is-a
  • 生命周期:子类对象包含父类部分,继承行为
  • 原则:接口继承优先于实现继承,组合优先于实现继承

2⃣ has-a(聚合/组合)

class Switch {
    std::vector<Port> ports; // 组合,Port 生命周期依赖 Switch
};
class Port {
    const Switch& host;      // 聚合,生命周期独立,显示依赖
    PortId id;
    Bandwidth bandwidth;
};
  • 组合 *--:Switch 拥有多个 Port(Port 随 Switch 生命周期销毁)
  • 聚合 o--:Port 对 Switch 有依赖,但生命周期独立
  • C++ 映射
    • 组合 → 成员对象或智能指针
    • 聚合 → const 引用或指针

3⃣ has-many(多对多关系)

#include <vector>
class Repository {
    std::vector<Port*> ports; // 聚合,多端口,Repository 管理
public:
    void addPort(Port* p);
    Port* findPortById(PortId id) const;
};
  • 概念:对象拥有多个子对象
  • 容器选择
    • 生命周期依赖整体 → 使用值对象或智能指针
    • 生命周期独立 → 使用裸指针或引用
  • 依赖关系反转
    • 客户端依赖抽象 Repository 接口而不是具体实现

4⃣ dependency(依赖)

class ConnectionSetupService {
public:
    void setupConnection(const PeerNode& peer); // 临时依赖 PeerNode
};
  • 特点
    • 不影响对象存在性
    • 生命周期独立
    • 保持物理依赖与逻辑依赖一致(方法参数、返回值)

三、UML 类图示例

1..*

«interface»

EventHandler

ConnectionSetupService

Switch

Port

Repository

PeerNode

为了方便记忆把箭头指向的认为是被动的对象,没箭头和方块默认是带箭头的
比如箭头指向EventHandler EventHandler 就是被继承,箭头指向PeerNode,PeerNode 就是被依赖,Switch实体方块然后指向Port Port 就是被Switch 拥有,Port 空菱形+执行Switch Switch 被Port 引用。 被指向的对象是被动对象

理解

  • is-a:接口继承,ConnectionSetupService 是 EventHandler
  • has-a
    • 组合:Switch 拥有 Port,Port 随 Switch 生命周期销毁
    • 聚合:Port 引用 Switch,生命周期独立
  • has-many:Repository 管理多个 Port
  • dependency:方法参数 PeerNode,生命周期独立

四、C++ 表达关系设计原则

  1. is-a
    • 接口继承优先,减少实现继承
    • 子类实现父类行为,private 方法可实现接口隔离
  2. has-a
    • 显式化生命周期关系
    • 组合/聚合根据依赖关系选择成员类型(值对象/指针/引用)
  3. has-many
    • 选择合适容器
    • 使用 Repository 管理多实例
    • 遵循依赖反转原则 → 面向接口编程
  4. dependency
    • 临时使用,不影响对象存在性
    • 方法参数引用/指针
    • 保持逻辑依赖和物理依赖一致
      总结口诀

is-a 子类化,has-a 拥有关系,has-many 容器管理,dependency 临时用,组合生命周期依赖,聚合可独立存在。

C++ 领域模型“表达关系:is-a”

一、UML 类图解析(is-a)

Switch

PhysicalSwitch

VirtualSwitch

理解

  • is-a(继承 / 泛化):子类是父类的具体类型
  • 实例化优先表达数据变化
    • 通过创建不同类型对象(PhysicalSwitch / VirtualSwitch)实现不同数据特性
  • 行为变化通过多态表达
    • 虚函数 getBandExpandFactor(),子类覆写父类行为

二、C++ 代码解析

1⃣ Switch.h(父类)

class Switch {
public:
    // 构造函数:初始化端口数量
    Switch(unsigned int portNum) 
        : portNum(portNum) {}
    virtual ~Switch() {}  // 虚析构函数保证多态销毁
    // 对外行为接口
    Status expandBand();
private:
    // 子类必须实现的虚函数,行为变化通过多态表达
    virtual unsigned int getBandExpandFactor() const = 0;
private:
    unsigned int portNum; // 数据属性
};
理解
  1. 数据变化
    • portNum 是 Switch 的实例属性,实例化不同对象表示不同端口数量
  2. 行为变化
    • getBandExpandFactor() 是纯虚函数
    • 子类覆写实现不同行为 → 多态

2⃣ VirtualSwitch.h(子类)

class VirtualSwitch : public Switch {
public:
    VirtualSwitch(unsigned int portNum) 
        : Switch(portNum) {}
private:
    // 行为变化:虚函数覆写
    unsigned int getBandExpandFactor() const override {
        return 128; // 虚拟交换机带宽扩展因子
    }
};
  • is-a:VirtualSwitch 是 Switch → 可以通过 Switch* 或 Switch& 调用 polymorphic 行为
  • 数据变化:通过实例化传递不同 portNum
  • 行为变化:覆写 getBandExpandFactor()

3⃣ PhysicalSwitch.h(子类)

class PhysicalSwitch : public Switch {
public:
    PhysicalSwitch(unsigned int portNum) 
        : Switch(portNum) {}
private:
    // 行为变化:虚函数覆写
    unsigned int getBandExpandFactor() const override {
        return 64; // 物理交换机带宽扩展因子
    }
};
  • is-a:PhysicalSwitch 是 Switch
  • 行为变化:不同子类实现不同带宽扩展逻辑
  • 数据变化:通过实例化不同对象控制端口数量

三、设计原则总结


设计角度 实现方式 理解
数据变化 实例化不同子类对象 不同对象代表不同数据状态
行为变化 虚函数多态 子类覆写方法,实现不同行为
is-a 关系 继承(< –) 子类是父类的特化,实现父类接口
多态访问 父类指针或引用 对外统一接口,内部行为可变化

四、示例调用

Switch* sw1 = new PhysicalSwitch(4);
Switch* sw2 = new VirtualSwitch(8);
// 数据变化通过实例不同对象
std::cout << sw1->getBandExpandFactor(); // 输出 64
std::cout << sw2->getBandExpandFactor(); // 输出 128
delete sw1;
delete sw2;
  • 数据变化 → 构造不同实例(端口数不同)
  • 行为变化 → 多态调用 getBandExpandFactor()

五、总结

  1. is-a 表达关系
    • 子类继承父类 → 泛化/接口实现
    • UML <|--
    • 多态覆盖行为变化
  2. 数据变化优先实例化
    • 构造不同对象表示不同状态
    • 避免在父类中硬编码数据
  3. 行为变化使用多态
    • 父类定义虚函数
    • 子类覆写不同实现
    • 外部统一接口调用,无需关心具体类型
      ✓ 结论:
      数据变化靠实例化(不同对象),行为变化靠多态(虚函数覆写),Switch / PhysicalSwitch / VirtualSwitch 完整表达 is-a 关系,符合领域驱动设计原则。

C++ 领域模型“表达关系:继承”

一、UML 类图解析

AlarmReporter

AlarmDetector

Switch

Port

Bandwidth

理解

  1. AlarmDetector
    • 定义报警检测接口(纯虚类)
    • is-a 关系被 Switch 和 Port 实现
    • 对外暴露 hasAlarm() 方法
  2. Switch / Port
    • 继承 AlarmDetector → 公有继承(is-a)
    • 表示 Switch/Port 具有报警检测能力
    • 外部可通过 AlarmDetector 接口访问多态行为
  3. Port 组合 Bandwidth
    • 实心菱形 *--> 表示组合
    • Port 拥有 Bandwidth,生命周期绑定
  4. AlarmReporter 聚合 AlarmDetector
    • 空心菱形 o--> 表示依赖
    • AlarmReporter 可以使用多个 AlarmDetector 对象
    • 生命周期独立

二、C++ 代码解析

1⃣ AlarmDetector.h(接口)

DEF_INTERFACE(AlarmDetector)
{
    ABSTRACT(bool hasAlarm() const);
};
  • DEF_INTERFACEABSTRACT 封装纯虚类
  • 方法 hasAlarm()const 修饰,表示只读
  • 接口隔离原则:客户只依赖接口,不依赖具体实现

2⃣ Port.h(实现类)

#include "AlarmDetector.h"
#include "Bandwidth.h"
class Port : public AlarmDetector, private Bandwidth {
public:
    // 构造函数:初始化 PortId
    Port(PortId id);
    Connection* connect(const PeerNode& peer);
private:
    // 覆写接口方法,private化,禁止从具体类直接调用
    bool hasAlarm() const override;
private:
    PortId id;
    // 继承 Bandwidth 表示组合关系(生命周期绑定)
};

理解

  1. 公有继承 AlarmDetector
    • Port is-a AlarmDetector
    • 客户可以通过 AlarmDetector* / AlarmDetector& 调用 hasAlarm()
    • 覆写方法 private 化 → 外部不能直接调用具体类实现
  2. 私有继承 Bandwidth
    • 实现组合(组合关系 1:1)
    • 生命周期绑定 → Port 销毁时 Bandwidth 也销毁
    • 本质上是一种语法上的“组合”,而非接口继承

三、公有继承 vs 私有继承


继承类型 C++ 语法 语义 生命周期 使用场景
公有继承 class Child : public Parent is-a,子类是父类 子类包含父类 接口/角色继承,允许多态访问
私有继承 class Child : private Parent 实现细节,类似组合 子类控制父类生命周期 组合实现,行为复用,不暴露接口
保护继承 class Child : protected Parent 类内部使用继承接口 生命周期绑定 子类内部复用,外部不可访问父类接口

四、UML 与 C++ 映射总结


UML 符号 C++ 映射 理解
`< –` public 继承 is-a 关系,接口继承
*--> private 继承或成员对象 强生命周期绑定,组合关系
o--> 成员引用/指针 聚合,生命周期独立

五、设计原则总结

  1. 公有继承
    • 接口或角色继承
    • 客户通过父类接口访问多态行为
    • 覆写方法可 private → 避免直接调用具体类
  2. 私有继承
    • 实现组合语义,生命周期绑定
    • 不对外暴露接口,行为复用
    • 等价于组合的一种实现方式
  3. 组合 vs 聚合
    • 组合:实心菱形,子对象随整体销毁 → private Bandwidth 或成员对象
    • 聚合:空心菱形,子对象独立 → 成员引用或指针
  4. 多态与行为变化
    • 接口继承用于行为抽象
    • 子类覆写虚函数 → 实现不同行为

六、示例调用

Port p(PortId(1));
AlarmDetector* detector = &p;
// 调用多态接口
bool alarm = detector->hasAlarm();
// 外部无法直接访问 Bandwidth,因为是私有继承
// p.getBandwidth(); // 错误
  • 多态访问 → 公有继承 AlarmDetector
  • 组合实现 → 私有继承 Bandwidth,不对外暴露
  • 生命周期绑定 → Port 销毁时 Bandwidth 自动销毁
    总结口诀

公有继承 is-a 接口/角色,私有继承实现组合/行为复用,组合生命周期随整体,聚合依赖独立存在,多态行为通过接口访问。
表达关系:has-a

聚合 (Aggregation)

组合 (Composition)

聚合 (Aggregation)

1
1
1
1..*
1
0..1

Switch

Port

Bandwidth

Connection

C++ 领域模型组合与聚合实现模式

一、组合(Composition)与聚合(Aggregation)概念


关系类型 UML 符号 C++ 映射 生命周期语义 设计原则
组合(Composition) 实心菱形 *-- 成员对象 或 私有继承 private 子对象生命周期与宿主一致 强烈绑定,宿主销毁时子对象自动销毁
聚合(Aggregation) 空心菱形 o-- 引用 & 或 指针 * 子对象可独立存在 宿主与部分对象生命周期不同,依赖注入明确

二、组合优先推荐的实现方式

  1. 值包含(成员对象)
    • 子对象作为宿主成员对象声明
    • 生命周期与宿主一致
    • 不对外暴露成员内部实现
  2. 私有继承(private inheritance)
    • 相当于组合的一种语法实现
    • 子对象功能复用,但不暴露接口
    • 生命周期随宿主类
      示例:Port 私有继承 Bandwidth(组合)
class Port : private Bandwidth {  // Composition: Port 拥有 Bandwidth,生命周期绑定
public:
    Port(PortId id, const Switch& s);
    Status connect(const Connection& conn);  // 对外行为
    void release(const Connection& conn);
private:
    PortId id;             // 唯一标识
    Connection* conn;      // 聚合或依赖对象
    const Switch& host;    // 引用聚合,生命周期早于宿主
};

三、聚合实现原则

  1. 使用引用(&)
    • 当聚合对象 生命周期早于宿主
    • 通过构造函数注入(Constructor Dependency Injection)
    • 使用 const 保证只读访问
  2. 使用指针(*)
    • 当聚合对象 生命周期晚于宿主
    • 可以通过 setter 或接口更新(update)

四、Port.h 分析

class Port : private Bandwidth {
public:
    // 构造函数:Port 依赖 Switch,生命周期早于宿主
    Port(PortId id, const Switch& s);
    // 对外接口:建立连接
    Status connect(const Connection& conn);
    // 对外接口:释放连接
    void release(const Connection& conn);
private:
    PortId id;             // 实体标识
    Connection* conn;      // 聚合/依赖对象,生命周期晚于宿主
    const Switch& host;    // 引用聚合,生命周期早于宿主
};

理解

  1. 组合
    • private Bandwidth → Port 拥有 Bandwidth
    • 生命周期绑定
    • 外部不能直接访问 Bandwidth
  2. 聚合 / 依赖
    • const Switch& host → 生命周期早于宿主,通过构造函数注入
    • Connection* conn → 生命周期晚于宿主,可通过方法更新
  3. 行为接口
    • connect()release() 提供对外操作
    • 内部状态通过成员对象和指针维护

五、UML 类图示例

Port

-PortId id

-Connection* conn

-const Switch& host

Switch

Bandwidth

Connection

理解

  • 实心菱形 *-- → 组合,Port 拥有 Bandwidth
  • 空心菱形 o-- → 聚合,Port 引用 Switch
  • 依赖箭头 ..> → 临时/晚于宿主的对象,Port 依赖 Connection

六、设计原则总结

  1. 组合(Composition)
    • 强生命周期绑定,宿主销毁 → 子对象自动销毁
    • 优先使用值对象成员或私有继承
  2. 聚合(Aggregation)
    • 生命周期可独立
    • 生命周期早于宿主 → 使用引用 + 构造函数注入
    • 生命周期晚于宿主 → 使用指针 + update 接口
  3. 接口设计
    • 对外接口只暴露必要行为(connect/release)
    • 内部成员保持封装和生命周期管理
      口诀总结

组合强绑定,值包含或私有继承;聚合生命周期早用引用,晚用指针;行为接口最小化暴露,const 表示只读。

C++ 领域模型“表达关系:has-many”

一、has-many 的概念

  • has-many:一个对象拥有多个其他对象的关系
  • 依赖方向设计
    1. host -> items
      • 宿主持有多个子对象
      • 通常用指针或容器管理(std::vector<Item*> 或数组)
      • 宿主管理子对象生命周期
    2. item -> host
      • 子对象持有宿主的引用
      • 生命周期早于宿主
      • 构造函数注入,保证只读访问
  • 双向引用
    • 尽量避免直接双向引用,避免循环依赖
    • 可让 items 持有宿主接口的一个角色,减少耦合

二、C++ 代码分析(Port 与 Switch)

class Port : private Bandwidth { 
public: 
    Port(PortId id, const Switch& s); // 构造函数注入宿主
    Status connect(const Connection& conn);  
    void release(const Connection& conn); 
private: 
    PortId id;                        // 唯一标识
    Connection* conn[MAX_CONN_NUM];   // 容器管理多个 Connection → has-many
    const Switch& host;               // 宿主引用 → 生命周期早于 Port
};

理解

  1. host -> items
    • Switch 拥有多个 Port → 聚合关系
    • 生命周期可以独立或绑定
    • UML 用空心菱形 o-- "1..N" 表示
  2. Port -> host
    • host 引用宿主 Switch
    • 生命周期早于 Port,构造函数注入
    • 保证只读访问(const Switch&
  3. Port -> Connection
    • Port 维护多个 Connection → has-many
    • 通过指针数组 Connection* conn[MAX_CONN_NUM]
    • 聚合关系,Connection 生命周期可独立

三、UML 类图示例

聚合

组合

聚合

N
1..N
1

Switch

Port

Bandwidth

Connection

理解

  • 实心菱形 *-- → 组合
    • Port 拥有 Bandwidth,生命周期绑定
  • 空心菱形 o-- → 聚合
    • Switch 拥有多个 Port,Port 生命周期可以独立
    • Port 拥有多个 Connection,Connection 生命周期可以独立
  • 箭头方向
    • 宿主指向 items,表示管理多个对象
    • items 可以持有宿主引用,保证依赖和生命周期早于 items

四、设计原则总结

  1. has-many 宿主持有 items
    • 使用指针或容器
    • 宿主负责生命周期或管理
    • 容器大小可固定(数组)或动态(vector)
  2. items 持有宿主引用
    • 生命周期早于 items → 构造函数注入
    • 使用 const 保证只读
  3. 双向引用尽量避免
    • 如果必须,items 持有宿主接口而非完整类型
    • 减少循环依赖
  4. 组合 vs 聚合
    • 组合(Port -> Bandwidth) → 生命周期绑定
    • 聚合(Switch -> Port, Port -> Connection) → 生命周期可独立

五、示例调用

Switch sw;
Port p1(PortId(1), sw);
Port p2(PortId(2), sw);
// Port 连接多个 Connection
Connection c1, c2;
p1.connect(c1);
p1.connect(c2);
  • Switch -> Port → 聚合
  • Port -> Connection → 聚合
  • Port -> Bandwidth → 组合,内部管理
    口诀总结

宿主持有多个 items 用指针或容器(has-many);items 持有宿主引用,生命周期早于 items;组合实心菱形生命周期绑定,聚合空心菱形生命周期可独立。

dependency(依赖)

一、UML 图解析

use

1 .. N

Port

PeerNode

Connection

理解

1⃣ Port …> PeerNode(dependency)

  • 虚线箭头 ..> 表示依赖
  • 语义:Port 使用 PeerNode
  • 不是生命周期关系
  • 不是拥有关系
  • 只是“功能使用”
    dependency 是“功能需要”,不是“存在需要”。

2⃣ Port o-- Connection(聚合)

  • 空心菱形表示聚合
  • 表示 Port 持有多个 Connection
  • 生命周期可以独立

二、dependency 的本质

dependency 是“功能上的需要”,不是“存在上的需要”。
可以形式化理解:
如果对象 A 的存在不依赖 B,则:
Exist(A)⇏Exist(B) Exist(A) \not\Rightarrow Exist(B) Exist(A)Exist(B)
但如果 A 调用 B 的功能,则:
Use(A,B) Use(A,B) Use(A,B)
这就是 dependency。

三、C++ 中 dependency 的三种表达方式

1⃣ 参数依赖(use 关系)

最常见的 dependency 表达方式。

class PeerNode;  // 前置声明,减少物理依赖
class Port {
public:
    // 参数依赖
    Status connect(const PeerNode& peer) const;
};

说明

  • const PeerNode&
  • 表示:
    • 功能使用
    • 不修改对象
    • 不拥有对象
    • 生命周期独立

原则:

  • 尽量使用 引用
  • 加上 const
  • 不使用值传递(避免复制)
  • 不使用指针(除非允许为空)

2⃣ 返回值依赖(create 关系)

用于表达“创建关系”。

创建实体 → 返回指针

class Connection;
class Port {
public:
    Connection* createConnection(const PeerNode& peer);
};

为什么返回指针?

  • 实体有身份
  • 生命周期可独立
  • 可能由调用者管理

创建值对象 → 返回值类型

class Bandwidth {
public:
    explicit Bandwidth(unsigned int v);
};
Bandwidth Port::calculateBandwidth() const;

值对象:

  • 无身份
  • 可复制
  • 生命周期简单

四、减少 dependency 的物理依赖

1⃣ 使用前置声明

✗ 错误做法:

#include "PeerNode.h"

✓ 正确做法:

class PeerNode;  // 前置声明

前置声明即可满足:

  • 指针
  • 引用
  • 函数参数
  • 返回指针

2⃣ 何时必须 include?

只有在以下情况必须 include:

  • 需要对象大小(成员变量)
  • 需要内联实现
  • 需要访问具体成员
    否则:

头文件只声明,cpp文件包含

五、dependency vs aggregation 对比


特性 dependency aggregation
UML ..> o--
是否拥有 ✗ 不拥有 ✗ 不一定拥有
生命周期依赖 ✗ 无 ✗ 可能
表达方式 参数/返回值 成员变量
是否存储 ✗ 不存储 ✓ 存储

六、完整示例(整合)

// 前置声明
class PeerNode;
class Connection;
class Port {
public:
    // dependency:use 关系
    Status connect(const PeerNode& peer);
    // create 关系
    Connection* createConnection(const PeerNode& peer);
private:
    // 聚合:has-many
    Connection* connections[MAX_CONN_NUM];
};

七、设计原则总结

dependency 设计准则

  1. 只用于表达“功能使用”
  2. 尽量使用 const 引用
  3. 返回实体用指针
  4. 返回值对象用值
  5. 头文件用前置声明
  6. 不在头文件 include 依赖类

八、dependency 的一句话口诀

dependency 是功能需要,不是存在需要;参数用引用,返回看语义;减少物理依赖,头文件只声明。

九、与其他关系对比总结


关系 关键词 生命周期 是否拥有
继承 is-a 绑定
组合 has-a 强绑定
聚合 has-a 可独立
has-many 多个 可独立
dependency use 无关

领域模型在 C++ 中的核心难点 —— 生命周期设计(Lifecycle Design)

领域模型是否健壮,本质取决于:
对象的生命周期=领域语义+所有权模型 \text{对象的生命周期} = \text{领域语义} + \text{所有权模型} 对象的生命周期=领域语义+所有权模型
如果生命周期表达错,领域语义就会被破坏。

一、生命周期管理的本质

1⃣ 生命周期是什么?

对象从:
构造→使用→销毁 \text{构造} \rightarrow \text{使用} \rightarrow \text{销毁} 构造使用销毁
完整过程就是生命周期。
形式化表达:
Lifetime(O)=[tcreate,tdestroy) Lifetime(O) = [t_{create}, t_{destroy}) Lifetime(O)=[tcreate,tdestroy)

2⃣ 所有权是什么?

所有权是:

谁负责销毁对象?
如果对象 A 负责销毁对象 B:
Owner(A,B)=true Owner(A,B) = true Owner(A,B)=true

二、领域模型中的生命周期设计原则

原则一:生命周期必须表达领域语义

例如:

  • Switch 拥有 Port
  • Port 不拥有 PeerNode(只是 use)
    如果代码表达成:
PeerNode peer;  // 组合

那语义就错了。

原则二:所有权必须唯一(避免双重释放)

∑Owner(Oi,X)=1 \sum Owner(O_i, X) = 1 Owner(Oi,X)=1
一个对象只能有一个所有者。

三、Factory / Repository 模式

在领域模型中:

对象的创建与销毁不应该由业务类直接负责
而是交给:

  • Factory(创建)
  • Repository(生命周期 + 存储管理)

1⃣ Factory 示例

class Connection {
public:
    Connection(int id) : id_(id) {}
private:
    int id_;
};
class ConnectionFactory {
public:
    // 负责创建对象
    static Connection* create(int id) {
        return new Connection(id);
    }
};

解释

  • 隐藏构造细节
  • 隐藏内存分配
  • 业务类不直接 new

2⃣ Repository 示例

#include <vector>
#include <memory>
class ConnectionRepository {
public:
    Connection& add(int id) {
        storage_.push_back(std::make_unique<Connection>(id));
        return *storage_.back();
    }
private:
    std::vector<std::unique_ptr<Connection>> storage_;
};

解释

  • Repository 持有对象
  • 统一销毁
  • 业务类不关心 delete

四、嵌入式环境的特殊考虑

嵌入式系统通常:

  • 无堆
  • 或堆受限
  • 或必须可预测

1⃣ 重载 new / delete

用于:

  • 定制内存区域
  • 使用专用内存池
class MyObject {
public:
    static void* operator new(std::size_t size) {
        return my_memory_alloc(size);
    }
    static void operator delete(void* ptr) {
        my_memory_free(ptr);
    }
};

语义:

控制:
Memory=f(domainpolicy) Memory = f(domain_policy) Memory=f(domainpolicy)

2⃣ placement new(指定内存构造)

用于:

  • 预分配内存
  • 延迟构造
#include <new>
char buffer[sizeof(Connection)];
Connection* conn = new (buffer) Connection(42);

解释

  • 不分配内存
  • 只调用构造函数
  • 生命周期分离为:
    Memory_Lifetime≠Object_Lifetime Memory\_Lifetime \neq Object\_Lifetime Memory_Lifetime=Object_Lifetime

3⃣ Object Pool(对象池)

思想:

  • 静态确定最大数量
  • 预分配
  • 重复使用
class ConnectionPool {
public:
    Connection* allocate(int id) {
        for (auto& slot : pool_) {
            if (!slot.in_use) {
                slot.in_use = true;
                return new (&slot.storage) Connection(id);
            }
        }
        return nullptr;
    }
private:
    struct Slot {
        bool in_use = false;
        alignas(Connection) char storage[sizeof(Connection)];
    };
    Slot pool_[10];
};

数学理解

最大容量:
N=10 N = 10 N=10
时间复杂度:
Talloc=O(N) T_{alloc} = O(N) Talloc=O(N)
但:

  • 无堆分配
  • 可预测
  • 无碎片

五、时序图解析

Blue Object Green Object Blue Object Green Object Actor 调用 自调用/处理 调用/转发 Actor

详细理解

1⃣ User 调用 ObjectA

表示:

  • User 不拥有 ObjectA
  • 只是使用
    这是 dependency。

2⃣ ObjectA 自调用

说明:

  • 内部状态机
  • 内部处理流程

3⃣ ObjectA 调用 ObjectB

两种可能:

  • dependency(仅 use)
  • aggregation(成员对象)
    如果是 dependency:
void ObjectA::process(ObjectB& b);

如果是 aggregation:

class ObjectA {
    ObjectB b_;  // 生命周期绑定
};

激活条的含义

激活条表示:
Active(O,t)=true Active(O, t) = true Active(O,t)=true
对象处于执行状态。

六、生命周期的三种模型


模型 特点 C++ 表达
强绑定 同生共死 成员对象
弱绑定 可独立 指针 / unique_ptr
无绑定 仅使用 参数引用

七、领域生命周期口诀

语义先于代码,所有权必须唯一;
创建交给工厂,存储交给仓库;
嵌入式不用堆,对象池来管理;
内存和对象分离,placement 要牢记。

八、核心思想总结

生命周期设计的核心公式:
DomainCorrectness=OwnershipCorrectness Domain_Correctness = Ownership_Correctness DomainCorrectness=OwnershipCorrectness
如果所有权模型错误:

  • 会双删
  • 会泄漏
  • 会悬空
  • 会破坏领域一致性

领域模型实现的核心哲学生命周期

生命周期是“时间维度”,所有权是“责任维度”。
两者必须同时正确,领域模型才是安全的。

一、生命周期设计(Lifecycle Design)

1⃣ 拆分不同生命周期的领域对象

在领域中,不同对象往往对应不同业务时间段。
形式化表示:
Lifetime(Oi)=[tstart,tend) Lifetime(O_i) = [t_{start}, t_{end}) Lifetime(Oi)=[tstart,tend)
如果两个对象业务时间不同:
Lifetime(A)≠Lifetime(B) Lifetime(A) \ne Lifetime(B) Lifetime(A)=Lifetime(B)
那它们就不应该使用组合(强绑定)。

示例

  • 订单(Order)生命周期:下单 → 完成
  • 订单项(OrderItem)生命周期:创建 → 订单销毁
    显然:
    Lifetime(OrderItem)⊂Lifetime(Order) Lifetime(OrderItem) \subset Lifetime(Order) Lifetime(OrderItem)Lifetime(Order)
    因此:
  • Order 组合 OrderItem(成员对象或 unique_ptr)

2⃣ 生命周期必须与业务时间一致

原则:

领域对象的构造和销毁必须对应真实业务事件。
例如:

  • 用户注册 → 创建 User
  • 用户注销 → 销毁 User
    代码中必须明确表达:
class UserFactory {
public:
    static std::unique_ptr<User> create(UserId id) {
        return std::make_unique<User>(id);
    }
};

3⃣ 使用 Factory / Repository 管理生命周期

Factory 负责“开始”

class OrderFactory {
public:
    static std::unique_ptr<Order> create(OrderId id) {
        return std::make_unique<Order>(id);
    }
};

Repository 负责“存续 + 结束”

#include <vector>
#include <memory>
class OrderRepository {
public:
    Order& add(OrderId id) {
        orders_.push_back(OrderFactory::create(id));
        return *orders_.back();
    }
    void remove(OrderId id) {
        // 删除对应订单
    }
private:
    std::vector<std::unique_ptr<Order>> orders_;
};

说明:

  • Repository 持有 unique_ptr
  • 生命周期清晰可控
  • 销毁发生在容器删除时

二、所有权设计(Ownership Design)

1⃣ 所有权 ≠ 生命周期

重要区分:

  • 生命周期:对象活多久
  • 所有权:谁负责销毁
    形式化:
    Owner(A,B)=true⇒A负责B的销毁 Owner(A, B) = true \Rightarrow A 负责 B 的销毁 Owner(A,B)=trueA负责B的销毁
    但:
    Lifetime(A)≠Lifetime(B) Lifetime(A) \ne Lifetime(B) Lifetime(A)=Lifetime(B)
    未必相等。

三、unique_ptr:唯一所有权

1⃣ 表达语义

  • 唯一所有者
  • 生命周期绑定
  • 可转移
  • 不可共享
    数学表达:
    ∑Owner(Oi,X)=1 \sum Owner(O_i, X) = 1 Owner(Oi,X)=1

2⃣ 示例

#include <memory>
class Node {
public:
    Node(int v) : value(v) {}
private:
    int value;
};
class Tree {
public:
    void setRoot(std::unique_ptr<Node> root) {
        root_ = std::move(root);  // 转移所有权
    }
private:
    std::unique_ptr<Node> root_;  // 唯一所有者
};

说明:

  • Tree 是权威持有者
  • root 生命周期由 Tree 管理

四、shared_ptr:共享所有权

1⃣ 特点

  • 引用计数
  • 多个 owner
  • 生命周期隐式结束
    生命周期结束条件:
    RefCount(X)=0 RefCount(X) = 0 RefCount(X)=0

2⃣ 示例

#include <memory>
class Session {
};
std::shared_ptr<Session> createSession() {
    return std::make_shared<Session>();
}

风险:

  • 生命周期不清晰
  • 容易循环引用

3⃣ 循环引用问题

class A {
public:
    std::shared_ptr<B> b;
};
class B {
public:
    std::shared_ptr<A> a;  // 循环引用
};

结果:
RefCount(A)≥1∧RefCount(B)≥1 RefCount(A) \ge 1 \quad \land \quad RefCount(B) \ge 1 RefCount(A)1RefCount(B)1
永远不会释放。

五、weak_ptr:弱引用

1⃣ 语义

  • 不拥有
  • 不增加引用计数
  • 仅访问句柄

2⃣ 示例

#include <memory>
class A;
class B {
public:
    std::weak_ptr<A> parent;  // 不拥有
};

使用前必须检查:

if (auto p = parent.lock()) {
    // 安全访问
}

六、通过设计简化生命周期

最重要的一条:

通过拓扑顺序保证生命周期正确

示例:树结构

生成顺序:
Root→Child→Leaf Root \rightarrow Child \rightarrow Leaf RootChildLeaf
销毁顺序(自动):
Leaf→Child→Root Leaf \rightarrow Child \rightarrow Root LeafChildRoot

正确设计

#include <memory>
#include <vector>
class Node {
public:
    explicit Node(Node* parent = nullptr)
        : parent_(parent) {}
    void addChild(std::unique_ptr<Node> child) {
        children_.push_back(std::move(child));
    }
private:
    Node* parent_;  // 裸指针或引用,不拥有
    std::vector<std::unique_ptr<Node>> children_;  // 拥有子节点
};

解释

  • 父节点拥有子节点
  • 子节点只引用父节点
  • 生命周期单向流动
  • 无循环 ownership
    满足:
    Owner(Parent,Child)=true Owner(Parent, Child) = true Owner(Parent,Child)=true
    Owner(Child,Parent)=false Owner(Child, Parent) = false Owner(Child,Parent)=false

七、所有权设计黄金法则

1⃣ 尽量不共享所有权

共享所有权意味着:

  • 生命周期不可预测
  • 责任模糊

2⃣ 明确权威持有者

每个对象必须有一个“主所有者”。

3⃣ 只共享只读访问

如果只是访问:

void process(const Order& order);

不需要 shared_ptr。

4⃣ 优先使用 unique_ptr

原则:

能 unique 就不要 shared
能引用就不要指针
能值对象就不要堆对象

八、生命周期 + 所有权统一模型

可以总结为:
Domain_Safety=Correct(Lifetime)+Correct(Ownership) Domain\_Safety = Correct(Lifetime) + Correct(Ownership) Domain_Safety=Correct(Lifetime)+Correct(Ownership)

九、总结口诀

生命周期看业务时间,
所有权看销毁责任;
唯一持有用 unique,
共享持有要谨慎;
弱引用只做访问,
设计优于智能指针。

placement new 在领域模型与嵌入式系统中的使用模式

我们分层讲清楚:

一、什么是 placement new?

1⃣ 标准 new 做了两件事

new T(args);

等价于:
$$

  1. 分配内存 \
  2. 调用构造函数
    $$

2⃣ placement new 只做一件事

new (memory) T(args);

它只做:
Construct(T,memory) Construct(T, memory) Construct(T,memory)
不分配内存。

二、placement 的核心作用

1⃣ 在指定内存上构造对象

#include <new>
char buffer[sizeof(int)];
int* p = new (buffer) int(42);  // 在 buffer 上构造 int

解释:

  • buffer 是预分配内存
  • 构造发生在 buffer 上
  • 不使用堆

2⃣ 按系统字节对齐要求预占内存

对象必须满足:
Address≡0(modalignof(T)) Address \equiv 0 \pmod{alignof(T)} Address0(modalignof(T))
否则行为未定义。

正确做法

#include <new>
#include <cstddef>
alignas(double) char buffer[sizeof(double)];
double* p = new (buffer) double(3.14);

或者:

std::aligned_storage_t<sizeof(T), alignof(T)> storage;

三、为必须有参构造对象预占内存

有些对象:

  • 没有默认构造
  • 必须带参数
  • 又不能使用堆
    这时:
struct Device {
    Device(int id);
};
alignas(Device) char buffer[sizeof(Device)];
Device* d = new (buffer) Device(10);

四、为数组预占内存

alignas(Device) char buffer[10 * sizeof(Device)];
Device* arr = reinterpret_cast<Device*>(buffer);
for (int i = 0; i < 10; ++i) {
    new (&arr[i]) Device(i);
}

销毁必须手动调用:

for (int i = 0; i < 10; ++i) {
    arr[i].~Device();
}

五、和 union 结合实现静态多态

这部分是关键。
你给的类图:

AlarmReporter

«interface»

Reporter

CachedReporter

PromptReporter

语义:

  • Reporter 是接口
  • CachedReporter / PromptReporter 是实现
  • AlarmReporter 聚合一个 Reporter

六、传统做法(堆 + 多态)

class AlarmReporter {
public:
    AlarmReporter(std::unique_ptr<Reporter> r)
        : reporter_(std::move(r)) {}
private:
    std::unique_ptr<Reporter> reporter_;
};

问题:

  • 使用堆
  • 生命周期动态
  • 嵌入式不友好

七、嵌入式静态多态方案(placement + union)

1⃣ 定义接口

class Reporter {
public:
    virtual ~Reporter() = default;
    virtual Status report(const AlarmInfo&) = 0;
};

2⃣ 具体实现

class CachedReporter : public Reporter {
public:
    explicit CachedReporter(const Address& addr)
        : addr_(addr) {}
    Status report(const AlarmInfo&) override {
        // 缓存发送
    }
private:
    Address addr_;
};
class PromptReporter : public Reporter {
public:
    explicit PromptReporter(const Address& addr)
        : addr_(addr) {}
    Status report(const AlarmInfo&) override {
        // 立即发送
    }
private:
    Address addr_;
};

3⃣ 使用 union + placement

核心思想:

  • 预留足够大内存
  • 在其中构造某一种实现
  • 运行时只存在一种

计算最大大小

Size=max⁡(sizeof(CachedReporter),sizeof(PromptReporter)) Size = \max(sizeof(CachedReporter), sizeof(PromptReporter)) Size=max(sizeof(CachedReporter),sizeof(PromptReporter))

实现

#include <new>
#include <type_traits>
class AlarmReporter {
public:
    enum class Type {
        Cached,
        Prompt
    };
    AlarmReporter(Type t, const Address& addr)
        : type_(t)
    {
        if (t == Type::Cached) {
            reporter_ = new (&storage_) CachedReporter(addr);
        } else {
            reporter_ = new (&storage_) PromptReporter(addr);
        }
    }
    ~AlarmReporter() {
        reporter_->~Reporter();  // 手动析构
    }
    Status report(const AlarmInfo& info) {
        return reporter_->report(info);
    }
private:
    Type type_;
    using Storage =
        std::aligned_storage_t<
            sizeof(CachedReporter) > sizeof(PromptReporter)
                ? sizeof(CachedReporter)
                : sizeof(PromptReporter),
            alignof(CachedReporter)
        >;
    Storage storage_;
    Reporter* reporter_;
};

八、为什么叫“静态多态”?

因为:

  • 内存静态确定
  • 最大对象大小编译期确定
  • 不依赖堆
  • 无运行时分配
    但仍然有:
  • 虚函数分发
    可以表示为:
    Memory=Fixed Memory = Fixed Memory=Fixed
    Type=RuntimeSelect Type = Runtime_Select Type=RuntimeSelect

九、生命周期拆分

placement 下:
MemoryLifetime≠ObjectLifetime Memory_Lifetime \neq Object_Lifetime MemoryLifetime=ObjectLifetime
内存可能长期存在,但对象可能:
Construct→Destruct→Reconstruct Construct \rightarrow Destruct \rightarrow Reconstruct ConstructDestructReconstruct
这在嵌入式非常重要。

十、使用 placement 的注意事项

1⃣ 必须手动析构

object->~T();

2⃣ 必须保证对齐

Address≡0(modalignof(T)) Address \equiv 0 \pmod{alignof(T)} Address0(modalignof(T))

3⃣ 不能重复构造未析构对象

否则:
未定义行为。

十一、什么时候用 placement?

适用场景:

  • 禁止堆
  • 内存必须可预测
  • 对象数量固定
  • 对象类型有限
  • 高实时性要求

十二、总结口诀

placement 不分配,只构造;
对齐必须要满足;
生命周期可分离;
union 预留最大体;
析构必须手动调。

十三、核心思想总结

placement 的本质:
Object=Memory+Constructor Object = Memory + Constructor Object=Memory+Constructor
标准 new:
Object=Allocate+Constructor Object = Allocate + Constructor Object=Allocate+Constructor
placement:
Object=Constructor(Memory) Object = Constructor(Memory) Object=Constructor(Memory)

#include <iostream>
#include <new>
#include <type_traits>
#include <string>
// ======================
// 基础领域类型
// ======================
using Status = bool;
struct Address {
    std::string ip;
};
struct AlarmInfo {
    std::string message;
};
// ======================
// Reporter 接口
// ======================
class Reporter {
public:
    virtual ~Reporter() = default;
    virtual Status report(const AlarmInfo& info) = 0;
};
// ======================
// CachedReporter 实现
// ======================
class CachedReporter : public Reporter {
public:
    explicit CachedReporter(const Address& addr)
        : addr_(addr)
    {
        std::cout << "CachedReporter constructed\n";
    }
    ~CachedReporter() override {
        std::cout << "CachedReporter destroyed\n";
    }
    Status report(const AlarmInfo& info) override {
        std::cout << "[Cached] Send to " << addr_.ip
                  << " message: " << info.message << "\n";
        return true;
    }
private:
    Address addr_;
};
// ======================
// PromptReporter 实现
// ======================
class PromptReporter : public Reporter {
public:
    explicit PromptReporter(const Address& addr)
        : addr_(addr)
    {
        std::cout << "PromptReporter constructed\n";
    }
    ~PromptReporter() override {
        std::cout << "PromptReporter destroyed\n";
    }
    Status report(const AlarmInfo& info) override {
        std::cout << "[Prompt] Send to " << addr_.ip
                  << " message: " << info.message << "\n";
        return true;
    }
private:
    Address addr_;
};
// ======================
// AlarmReporter
// 使用 placement + 静态内存
// ======================
class AlarmReporter {
public:
    enum class Type {
        Cached,
        Prompt
    };
    AlarmReporter(Type t, const Address& addr)
        : type_(t)
    {
        if (t == Type::Cached) {
            reporter_ = new (&storage_) CachedReporter(addr);
        } else {
            reporter_ = new (&storage_) PromptReporter(addr);
        }
    }
    ~AlarmReporter() {
        // 手动调用析构函数
        reporter_->~Reporter();
    }
    Status report(const AlarmInfo& info) {
        return reporter_->report(info);
    }
private:
    Type type_;
    // 计算最大对象大小
    static constexpr std::size_t MaxSize =
        sizeof(CachedReporter) > sizeof(PromptReporter)
            ? sizeof(CachedReporter)
            : sizeof(PromptReporter);
    static constexpr std::size_t MaxAlign =
        alignof(CachedReporter) > alignof(PromptReporter)
            ? alignof(CachedReporter)
            : alignof(PromptReporter);
    using Storage = std::aligned_storage_t<MaxSize, MaxAlign>;
    Storage storage_;     // 静态内存
    Reporter* reporter_;  // 指向 placement 构造对象
};
// ======================
// main 测试
// ======================
int main() {
    Address addr{"192.168.1.1"};
    AlarmInfo info{"Temperature too high"};
    {
        std::cout << "---- Cached Mode ----\n";
        AlarmReporter reporter(
            AlarmReporter::Type::Cached, addr);
        reporter.report(info);
    } // 作用域结束,手动析构触发
    {
        std::cout << "\n---- Prompt Mode ----\n";
        AlarmReporter reporter(
            AlarmReporter::Type::Prompt, addr);
        reporter.report(info);
    }
    return 0;
}
Storage (Stack) PromptReporter CachedReporter AlarmReporter main() Storage (Stack) PromptReporter CachedReporter AlarmReporter main() ---- Cached Mode ---- 作用域结束 (Destruction) ---- Prompt Mode ---- 构造 AlarmReporter(Type::Cached, addr) 确定 MaxSize 和 MaxAlign placement new (在 storage_ 上构造) 返回对象指针 返回实例 report(info) reporter_->>report(info) [Cached] Send to... (Status) ~AlarmReporter() reporter_->>~Reporter() (手动调用析构) 清理资源 构造 AlarmReporter(Type::Prompt, addr) placement new (在 storage_ 上构造) 返回对象指针 返回实例 report(info) reporter_->>report(info) [Prompt] Send to... (Status) ~AlarmReporter() reporter_->>~Reporter() 清理资源
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐