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

多范式融合 C++ 软件设计解析

核心思想:

不依赖单一范式,而是根据场景选择最合适的范式和 C++ 特性来解决问题。

1⃣ 多范式设计原则

  1. 分场景灵活选择范式
    • 指令式、过程式、面向对象、函数式、响应式等范式各有优势
    • 根据问题域选择合适的范式,而不是“一统江湖”
  2. 领域驱动设计(DDD)视角
    • 将软件划分为不同的 域(Domain)/ 层(Layer)
    • 每个域可以采用不同范式:
      • 核心业务逻辑 → 面向对象或函数式
      • 流程控制 → 过程式或指令式
      • 数据流 / 异步事件 → 响应式
  3. C++ 特性支撑
    • 类/对象、模板、Lambda、智能指针、并发库等
    • 不滥用语法特性,避免复杂度和不可维护性
  4. DCI 架构实现多范式融合
    • D (Data):领域对象 → 面向对象
    • C (Context):业务场景 → 过程式 / 函数式组合
    • I (Interaction):角色/职责 → 高阶函数 / Lambda / 闭包

2⃣ C++ 示例:多范式融合 + DCI

我们用一个 电商购买流程 作为例子:

#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <vector>
//////////////////////
// D: Data - 领域对象
//////////////////////
class User {
public:
    std::string name;
    int balance;
    User(std::string n, int b) : name(n), balance(b) {}
};
class Product {
public:
    std::string name;
    int price;
    Product(std::string n, int p) : name(n), price(p) {}
};
//////////////////////
// C: Context - 业务场景
//////////////////////
class PurchaseContext {
public:
    User& user;
    Product& product;
    PurchaseContext(User& u, Product& p) : user(u), product(p) {}
    // I: Interaction - 使用 Lambda 封装角色行为
    std::function<void()> checkout() {
        return [this]() {
            if(user.balance >= product.price) {
                user.balance -= product.price;
                std::cout << user.name << " purchased " 
                          << product.name 
                          << ", remaining balance: " << user.balance 
                          << std::endl;
            } else {
                std::cout << user.name << " has insufficient balance." << std::endl;
            }
        };
    }
};
//////////////////////
// 程序入口
//////////////////////
int main() {
    // 数据域
    User alice("Alice", 100);
    Product book("C++ Book", 30);
    // 场景域
    PurchaseContext ctx(alice, book);
    // 执行交互
    auto action = ctx.checkout();
    action(); // 执行 Lambda 闭包
    return 0;
}

理解:

  1. D(Data)
    • UserProduct → 面向对象封装数据
    • 提供属性、但不包含业务逻辑
  2. C(Context)
    • PurchaseContext → 封装业务场景
    • 负责协调数据对象和角色交互
  3. I(Interaction)
    • checkout() 返回 Lambda 闭包
    • 封装操作逻辑 → 高阶函数 + 函数式设计
    • 执行时绑定上下文,实现职责与数据分离
  4. 分范式使用
    • 面向对象 → 数据封装
    • 函数式 → 闭包、Lambda 封装角色行为
    • 过程式 → main() 里调用上下文执行操作

3⃣ 公式化描述

业务流程可抽象为:
Result=I(C(D)) \text{Result} = I(C(D)) Result=I(C(D))

  • DDD → 数据对象域 (User, Product)
  • CCC → 业务上下文/场景 (PurchaseContext)
  • III → 角色交互/操作 (checkout Lambda)
  • 执行流程:
    User,Product→PurchaseContextcheckout()→invokeResult User, Product \xrightarrow{PurchaseContext} \text{checkout()} \xrightarrow{invoke} Result User,ProductPurchaseContextcheckout()invokeResult

4⃣ 优点总结

  1. 分域使用范式 → 灵活选择最佳策略
  2. 职责与数据分离 → 易于维护和测试
  3. 闭包实现高阶函数 → 可组合、可扩展
  4. 避免单一范式拥坌 → 面向对象、函数式、过程式协同

5⃣ 可扩展示例

  • 可以增加 事件流 / 响应式处理
std::vector<std::function<void()>> eventQueue;
eventQueue.push_back(ctx.checkout());
// 异步执行
for(auto& e : eventQueue) e();
  • 对应 RxCpp 风格:
    Eevent=subscribe(checkout closure) E_{\text{event}} = \text{subscribe}(\text{checkout closure}) Eevent=subscribe(checkout closure)
    总结
  • 多范式融合 C++ 软件设计的关键是 “按域/层选择范式”
  • DCI 架构提供自然的 Data → Context → Interaction 框架
  • Lambda/闭包 + 面向对象 + 过程式结合 → 可组合、可扩展
  • 响应式事件流可以进一步支持并发和异步处理

DCI(Data–Context–Interaction)设计过程

1⃣ Context1 — 生命周期管理 (Lifecycle)

Context1 (Lifecycle)

Create // onCreated

Initialize

All Partner Online

Partner Offline

Timeout / Partner Offline

Finish

CREATED

INITIALIZED

STARTWORKED

interface ILifecycle


+ onCreated()
+ onFinished()
+ onTimeout()
+ OnlineOf(Whom&)
+ OfflineOf(Whom&)

LifecycleImplA

LifecycleImplB

理解

  1. Context1 代表生命周期管理的上下文。
  2. 状态机从 创建 (CREATED)初始化 (INITIALIZED)开始工作 (STARTWORKED)结束
  3. ILifecycle 定义了生命周期接口:
    • onCreated() — 创建时触发
    • onFinished() — 完成时触发
    • onTimeout() — 超时触发
    • OnlineOf(Whom&) / OfflineOf(Whom&) — 管理伙伴上线/离线状态
  4. LifecycleImplA/B 是具体实现类,可以复用在不同业务对象上。
    公式理解:如果定义 Context 生命周期长度为 TlifeT_\text{life}Tlife,且初始化耗时 tinitt_\text{init}tinit,则有效工作时间 TworkT_\text{work}Twork 可以近似表示为:
    Twork=Tlife−tinit−ttimeout T_\text{work} = T_\text{life} - t_\text{init} - t_\text{timeout} Twork=Tlifetinitttimeout

2⃣ Context2 — Handler 与 Stream

Context2 (Handler & Stream)

Stream Choreography

trigger

choreography

Scheduler

Stream

Processor

// reactive style
.on().take().map().subscribe()

interface IHandler


+ Status proc(Msg&)

MsgHandler

interface ISchedulable

FixScheduler

理解

  1. Context2 负责消息处理和数据流调度。
  2. IHandler 定义了 proc(Msg&) 消息处理接口。
  3. ISchedulable 提供调度能力。
  4. MsgHandlerFixScheduler 是接口实现类。
  5. Stream Choreography 体现响应式编程思想:
    • 数据从 触发者 (Scheduler) 流向 Stream
    • Stream 根据 Processor 处理
    • 使用 .on().take().map().subscribe() 类似 Rx 风格操作
      公式表示:若每条消息处理耗时 tproct_\text{proc}tproc,处理速率为 Rproc=1/tprocR_\text{proc} = 1/t_\text{proc}Rproc=1/tproc,则系统处理能力约为:
      Csystem=∑iRproc,i C_\text{system} = \sum_i R_{\text{proc},i} Csystem=iRproc,i

3⃣ Role & Implementations — 角色和业务对象

Role & Implementations

Infrastructure

create

save / find

Factory

L_ImplA

Object: BusinessObject


// central business object

L_ImplB

M_Handler

F_Scheduler

Repository

理解

  1. BusinessObject (BizObj) 是 DCI 中的 Data,即数据对象。
  2. 各个 Role 实现类(LifecycleImpl, MsgHandler, Scheduler)通过绑定 BizObj 实现交互逻辑。
  3. 基础设施(Infrastructure)提供:
    • Factory — 创建 BizObj
    • Repository — 保存/查询 BizObj
      公式解释:若业务对象状态向量为 s=[s1,s2,...,sn]\mathbf{s} = [s_1, s_2, ..., s_n]s=[s1,s2,...,sn],角色作用函数为 frole(s)f_\text{role}(\mathbf{s})frole(s),则交互后的新状态为:
      s′=frole(s) \mathbf{s}' = f_\text{role}(\mathbf{s}) s=frole(s)

4⃣ Combination — 上下文组合与交互

Combination

interacts

use

use

Context 1

Context 2

L_ImplA

M_Handler

L_ImplB

F_Scheduler

理解

  1. 上下文组合体现 DCI 中 Context 驱动 Role 交互
    • Context1 管理生命周期
    • Context2 管理消息和流
    • 它们通过 BizObj 共享状态和功能
  2. 交互逻辑
    • LifecycleImplA 使用 MsgHandler
    • LifecycleImplB 使用 FixScheduler
      公式表示:假设上下文间交互函数为 g(C1,C2)g(C_1, C_2)g(C1,C2),则系统整体行为可抽象为:
      SystemBehavior=g(Context1,Context2) \text{SystemBehavior} = g(\text{Context1}, \text{Context2}) SystemBehavior=g(Context1,Context2)

5⃣ 总结理解

  • Data(数据):BizObj — 核心业务对象
  • Context(上下文):Context1 / Context2 — 组织角色交互的环境
  • Interaction / Role(角色交互):LifecycleImpl, MsgHandler, Scheduler — 动态绑定到 BizObj,实现特定功能
  • Infrastructure(基础设施):Factory, Repository — 创建和持久化数据
  • DCI 核心思想
    1. 数据对象和角色职责分离
    2. 上下文定义角色组合与交互流程
    3. 使用上下文驱动对象行为,不污染数据对象本身

Role & Implementations

Context2 (Handler & Stream)

Context1 (Lifecycle)

Infrastructure

Create // onCreated

Initialize

All Partner Online

Partner Offline

Timeout / Partner Offline

Finish

create

save / find

use

use

Combination

interacts

Context 1

Context 2

Stream Choreography

trigger

choreography

Scheduler

Stream

Processor

// reactive style
.on().take().map().subscribe()

CREATED

INITIALIZED

STARTWORKED

interface ILifecycle


+ onCreated()
+ onFinished()
+ onTimeout()
+ OnlineOf(Whom&)
+ OfflineOf(Whom&)

LifecycleImplA

LifecycleImplB

interface IHandler


+ Status proc(Msg&)

interface ISchedulable

MsgHandler

FixScheduler

Object: BusinessObject


// central business object

Factory

Repository

/**
 * ============================================================
 * DCI (Data-Context-Interaction) 完整示例 —— 超详细注释版
 * ============================================================
 *
 * 架构分层:
 *
 *   D —— Data
 *       BusinessObject
 *       只保存状态,不包含业务逻辑
 *
 *   I —— Interaction (Role)
 *       LifecycleImplA / MsgHandler / FixScheduler
 *       角色 = 给数据“赋予行为”
 *
 *   C —— Context
 *       Context1_Lifecycle
 *       Context2_HandlerStream
 *       负责组织角色之间的协作
 *
 *   Infra —— 基础设施
 *       Factory / Repository
 *
 * 关键思想:
 *
 *      行为 ∉ 数据
 *      行为 ∈ 角色
 *      流程 ∈ 上下文
 *
 */
#include <iostream>
#include <functional>
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
#include <stdexcept>
#include <algorithm>
#include <climits>
///////////////////////////////////////////////////////////////
// SECTION 1 — DATA 层(D)
// 纯数据对象,不包含任何业务逻辑
///////////////////////////////////////////////////////////////
struct Msg {
    int id;                 // 消息编号
    std::string payload;    // 消息内容
};
// 前向声明
class BusinessObject;
using BizObjPtr = std::shared_ptr<BusinessObject>;
/**
 * BusinessObject —— DCI 中的 Data
 *
 * 设计原则:
 *  - 不包含行为
 *  - 不知道生命周期
 *  - 不知道消息处理
 *  - 只保存状态
 */
class BusinessObject {
public:
    BusinessObject(int id, std::string name)
        : id_(id), name_(std::move(name)) {}
    int id() const { return id_; }
    std::string name() const { return name_; }
    // ===== 纯状态字段 =====
    std::string status = "idle";  // 当前业务状态
    int msgCount = 0;             // 已处理消息数量
private:
    int id_;               // 唯一标识
    std::string name_;     // 名称
};
///////////////////////////////////////////////////////////////
// SECTION 2 — INFRA 层
///////////////////////////////////////////////////////////////
/**
 * Repository —— 模拟持久化层
 *
 * 作用:
 *   - 保存业务对象
 *   - 查询业务对象
 *
 * 注意:
 *   这属于基础设施层,不属于 DCI 核心
 */
class Repository {
public:
    void save(BizObjPtr obj) {
        store_[obj->id()] = obj;
        std::cout << "[Repository] save id=" << obj->id() << "\n";
    }
    BizObjPtr find(int id) {
        auto it = store_.find(id);
        if (it == store_.end())
            throw std::runtime_error("not found");
        return it->second;
    }
private:
    std::unordered_map<int, BizObjPtr> store_;
};
/**
 * Factory —— 创建 Data 对象
 *
 * 设计意义:
 *   把对象创建与业务逻辑分离
 */
class Factory {
public:
    Factory(Repository& repo)
        : repo_(repo), nextId_(1) {}
    BizObjPtr create(const std::string& name) {
        auto obj = std::make_shared<BusinessObject>(nextId_++, name);
        repo_.save(obj);
        return obj;
    }
private:
    Repository& repo_;
    int nextId_;
};
///////////////////////////////////////////////////////////////
// SECTION 3 — Interaction 层(Role)
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
// 生命周期角色接口
///////////////////////////////////////////////////////////////
enum class LifecycleState {
    CREATED,
    INITIALIZED,
    STARTWORKED,
    FINISHED
};
/**
 * ILifecycle —— 角色接口
 *
 * 定义:
 *   这个角色“能做什么”
 */
class ILifecycle {
public:
    virtual ~ILifecycle() = default;
    virtual void onCreated() = 0;
    virtual void onFinished() = 0;
    virtual void onTimeout() = 0;
    virtual void onlineOf(const std::string&) = 0;
    virtual void offlineOf(const std::string&) = 0;
};
/**
 * LifecycleImplA —— 状态机实现
 *
 * 这是一个“角色”
 * 它持有 Data 引用
 * 它改变 Data 的状态
 *
 * 关键:
 *   数据对象不知道状态机
 *   状态机属于角色
 */
class LifecycleImplA : public ILifecycle {
public:
    explicit LifecycleImplA(BizObjPtr obj)
        : obj_(obj), state_(LifecycleState::CREATED) {}
    void onCreated() override {
        state_ = LifecycleState::CREATED;
        obj_->status = "created";   // 修改 Data
        log("CREATED");
    }
    void initialize() {
        state_ = LifecycleState::INITIALIZED;
        obj_->status = "initialized";
        log("INITIALIZED");
    }
    void onlineOf(const std::string& whom) override {
        partners_.push_back(whom);
        // 如果两个伙伴上线 → 开始工作
        if (partners_.size() >= 2) {
            state_ = LifecycleState::STARTWORKED;
            obj_->status = "working";
            log("STARTWORKED");
        }
    }
    void offlineOf(const std::string& whom) override {
        partners_.erase(
            std::remove(partners_.begin(), partners_.end(), whom),
            partners_.end()
        );
    }
    void onFinished() override {
        state_ = LifecycleState::FINISHED;
        obj_->status = "finished";
        log("FINISHED");
    }
    void onTimeout() override {
        state_ = LifecycleState::FINISHED;
        obj_->status = "timeout";
        log("TIMEOUT");
    }
private:
    BizObjPtr obj_;  // 被注入的数据
    LifecycleState state_;
    std::vector<std::string> partners_;
    void log(const std::string& s) {
        std::cout << "[Lifecycle] " << s << "\n";
    }
};
///////////////////////////////////////////////////////////////
// 消息处理角色
///////////////////////////////////////////////////////////////
enum class Status { OK, ERROR };
class IHandler {
public:
    virtual ~IHandler() = default;
    virtual Status proc(const Msg&) = 0;
};
/**
 * MsgHandler —— 消息处理角色
 *
 * 作用:
 *   读取数据
 *   修改数据
 */
class MsgHandler : public IHandler {
public:
    explicit MsgHandler(BizObjPtr obj)
        : obj_(obj) {}
    Status proc(const Msg& msg) override {
        // 修改 Data 的状态
        obj_->msgCount++;
        std::cout
            << "[Handler] msg#"
            << msg.id
            << " payload="
            << msg.payload
            << " total="
            << obj_->msgCount
            << "\n";
        return Status::OK;
    }
private:
    BizObjPtr obj_;
};
///////////////////////////////////////////////////////////////
// Scheduler 角色
///////////////////////////////////////////////////////////////
class ISchedulable {
public:
    virtual ~ISchedulable() = default;
    virtual void schedule(std::function<void()>, int) = 0;
    virtual void stop() = 0;
};
class FixScheduler : public ISchedulable {
public:
    explicit FixScheduler(BizObjPtr obj)
        : obj_(obj) {}
    void schedule(std::function<void()> task, int interval) override {
        std::cout
            << "[Scheduler] interval="
            << interval
            << "\n";
        // 简化为立即执行一次
        task();
    }
    void stop() override {
        std::cout << "[Scheduler] stopped\n";
    }
private:
    BizObjPtr obj_;
};
///////////////////////////////////////////////////////////////
// SECTION 4 — Context 层(C)
///////////////////////////////////////////////////////////////
/**
 * Context1 —— 生命周期上下文
 *
 * 职责:
 *   组织 Lifecycle 角色的调用顺序
 *
 * 关键:
 *   上下文定义“流程”
 */
class Context1_Lifecycle {
public:
    Context1_Lifecycle(BizObjPtr obj,
                       std::shared_ptr<LifecycleImplA> lc)
        : obj_(obj), lc_(lc) {}
    void run() {
        lc_->onCreated();
        lc_->initialize();
        lc_->onlineOf("A");
        lc_->onlineOf("B");
        lc_->onFinished();
    }
private:
    BizObjPtr obj_;
    std::shared_ptr<LifecycleImplA> lc_;
};
/**
 * Context2 —— 消息处理上下文
 *
 * 职责:
 *   组织 Handler + Scheduler
 */
class Context2_HandlerStream {
public:
    Context2_HandlerStream(
        BizObjPtr obj,
        std::shared_ptr<MsgHandler> handler,
        std::shared_ptr<FixScheduler> scheduler)
        : obj_(obj),
          handler_(handler),
          scheduler_(scheduler) {}
    void run() {
        std::vector<Msg> inbox = {
            {1,"hello"},
            {2,"world"}
        };
        for (auto& m : inbox) {
            handler_->proc(m);
        }
        scheduler_->schedule([&](){
            std::cout << "msgCount="
                      << obj_->msgCount
                      << "\n";
        }, 500);
        scheduler_->stop();
    }
private:
    BizObjPtr obj_;
    std::shared_ptr<MsgHandler> handler_;
    std::shared_ptr<FixScheduler> scheduler_;
};
///////////////////////////////////////////////////////////////
// MAIN —— 组装所有组件
///////////////////////////////////////////////////////////////
int main() {
    Repository repo;
    Factory factory(repo);
    // 创建 Data
    auto biz = factory.create("OrderProcessor");
    // 创建角色(注入 Data)
    auto lc = std::make_shared<LifecycleImplA>(biz);
    auto handler = std::make_shared<MsgHandler>(biz);
    auto scheduler = std::make_shared<FixScheduler>(biz);
    // 创建 Context
    Context1_Lifecycle c1(biz, lc);
    Context2_HandlerStream c2(biz, handler, scheduler);
    // 执行上下文
    c1.run();
    c2.run();
    repo.save(biz);
    std::cout
        << "Final status="
        << biz->status
        << " msgCount="
        << biz->msgCount
        << "\n";
}

这份代码已经是一个结构非常清晰的 DCI 示例

一、DCI 的核心思想到底是什么?

DCI 不是“把类拆开”这么简单。
它的本质是:

对象只表示“是什么”(Data)
行为来自“它在当前场景中扮演什么角色”(Role)
场景由 Context 组织

可以用一个抽象公式表示:
Behavior=Context(Role(Data)) Behavior = Context(Role(Data)) Behavior=Context(Role(Data))
或者更直观一点:
SystemBehavior=C(D,R) SystemBehavior = C(D, R) SystemBehavior=C(D,R)

  • DDD = Data(数据对象)
  • RRR = Role(角色行为)
  • CCC = Context(流程组织者)

二、你这份代码中的 DCI 映射

我们逐层拆解。

① Data —— BusinessObject

class BusinessObject {
    std::string status;
    int msgCount;
};

它只做三件事:

  • 保存 id
  • 保存 name
  • 保存状态数据
    它:
  • 不知道生命周期状态机
  • 不知道如何处理消息
  • 不知道调度器
  • 不知道上下文
    它只是“状态容器”。
    这就是 DCI 的第一原则:

数据模型不承载业务流程。

② Role —— Interaction(行为的载体)

例如:

class LifecycleImplA
class MsgHandler
class FixScheduler

这些类有一个共同点:

BizObjPtr obj_;

它们:

  • 不拥有数据
  • 只“借用”数据
  • 修改数据
  • 读取数据

举个关键点:

obj_->msgCount++;

Data 本身没有“处理消息”的能力。
处理能力来自 MsgHandler 这个角色。
这说明:

行为是外部赋予的,而不是内建的。

③ Context —— 行为的编排者

例如:

class Context1_Lifecycle
class Context2_HandlerStream

它们:

  • 决定调用顺序
  • 决定角色如何协作
  • 决定执行流程
    比如:
lc_->onCreated();
lc_->initialize();
lc_->onlineOf("A");
lc_->onlineOf("B");
lc_->onFinished();

Data 不知道这些流程。
Role 也不知道整个业务流程。
流程只存在于 Context。
这就是 DCI 最核心的分离:

负责什么
Data状态
Role行为
Context流程

三、DCI 的本质:对象 ≠ 行为

在传统 OOP 中:

class Order {
    void process();
    void finish();
};

行为和数据绑死在一起。
在 DCI 中:

Order   // 只是数据
OrderProcessorRole
OrderLifecycleRole
OrderBillingRole

一个对象可以在不同 Context 中扮演不同角色。
这意味着:
SameData+DifferentContext→DifferentBehavior SameData + DifferentContext → DifferentBehavior SameData+DifferentContextDifferentBehavior

四、你这份代码真正体现的核心哲学

1⃣ 行为是“动态赋予”的

auto lc = std::make_shared<LifecycleImplA>(biz);

你在 main 中“注入角色”。
这意味着:

  • 角色不是数据固有的
  • 角色是场景决定的
    这就是 DCI 的“动态建模能力”。

2⃣ Context 决定系统行为

看 main:

c1.run();
c2.run();

执行顺序决定系统最终状态。
可以抽象成:
FinalState=C2(C1(D)) FinalState = C_2(C_1(D)) FinalState=C2(C1(D))
先生命周期,再消息处理。
如果顺序改变,结果可能不同。

3⃣ 角色之间不直接耦合

LifecycleImplA 不知道 MsgHandler。
MsgHandler 不知道 Lifecycle。
它们唯一共享的是:

BusinessObject

这叫:

通过数据对象进行间接协作。

五、DCI 相比传统 OOP 的核心优势

① 降低类爆炸

传统:

OrderWithLifecycleAndHandlerAndScheduler

DCI:

Order (Data)
+ LifecycleRole
+ HandlerRole
+ SchedulerRole

角色可复用。

② 避免“胖对象”

Data 不再变成上千行代码。

③ 场景驱动设计

系统行为可以形式化为:
Behavior=f(Context,Data) Behavior = f(Context, Data) Behavior=f(Context,Data)
而不是:
Behavior=f(Data) Behavior = f(Data) Behavior=f(Data)

六、DCI 的哲学核心一句话

对象本身没有行为,
行为来自它在某个场景中的角色。

七、你这段代码是否“真正 DCI”?

是的,已经是比较正统的 DCI:
✓ Data 不含行为
✓ Role 持有 Data
✓ Context 组织流程
✓ 角色运行时注入
如果你再往前一步,可以:

  • 把 Role 动态注册到 Data(更纯粹)
  • 使用模板消除虚函数
  • 使用角色容器支持多角色组合

八、总结一句话

你这段代码的 DCI 本质可以写成:
系统=上下文驱动的数据状态变换 系统 = 上下文驱动的数据状态变换 系统=上下文驱动的数据状态变换
或者更抽象:
Dt+1=Context(Role(Dt)) D_{t+1} = Context(Role(D_t)) Dt+1=Context(Role(Dt))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值