全局变量在领域建模中特别危险,因其破坏领域边界、导致状态分散、修改源难追踪、并发不安全,并使业务概念退化为裸数据;应将其重构为有身份、责任和边界的领域对象。

为什么全局变量在领域建模中特别危险 全局变量表面方便,实际会悄悄破坏领域边界:状态散落各处、难以追踪修改来源、并发时极易出错,更关键的是——它让“订单”“用户”“库存”这些本该自包含的业务概念,退化成一堆裸数据和零散函数。C++ 没有内置的领域模型语法糖,但完全可以用类封装 + 明确生命周期 + 限制访问来重建语义完整性。
核心原则不是“消灭所有全局”,而是“把全局变量变成有身份、有责任、有边界的领域对象”。比如,别用 g_current_user_id 和 g_pending_orders,改用一个 UserSession 类管理当前用户上下文,它内部持有 m_user_id、m_cart、m_permissions,且只暴露 canPlaceOrder() 这样的领域行为,不暴露原始字段。
如何把零散变量打包成单个领域对象
先识别变量之间的语义归属关系,再按“谁拥有谁”“谁负责维护谁”聚类:
- 把所有与“当前登录用户”相关的变量(ID、token、角色、偏好设置)收进
UserContext 类,构造时强制传入必要参数,避免默认构造出无效实例
- 把“系统配置项”(超时时间、重试次数、API 地址)抽成
SystemConfig,用 static const SystemConfig& instance() 提供单例访问,但禁止外部修改——所有字段设为 const 或仅通过 init() 一次性加载
- 把“运行时统计”(请求计数、错误率、缓存命中数)放进
TelemetryRegistry,用 std::atomic 成员保证线程安全,对外只提供 incrementRequestCount() 这类带业务含义的方法,不暴露原始计数器变量
UserContext 类,构造时强制传入必要参数,避免默认构造出无效实例SystemConfig,用 static const SystemConfig& instance() 提供单例访问,但禁止外部修改——所有字段设为 const 或仅通过 init() 一次性加载TelemetryRegistry,用 std::atomic 成员保证线程安全,对外只提供 incrementRequestCount() 这类带业务含义的方法,不暴露原始计数器变量关键不是“换个名字”,而是让对象承担起对应领域的职责。例如 InventoryManager 不该只是存个 m_stock_map,还要提供 reserveItem(const ItemId&, int qty),并在内部校验库存是否充足、是否已锁定——这一步就把逻辑从调用方手里收了回来。
怎么防止领域对象退化回“高级全局变量”
最容易踩的坑是:类有了,但全是 public 成员或 getter/setter 泛滥,结果跟全局变量没区别。
- 禁用
public 数据成员;所有字段声明为 private,只在真正需要时提供受控的访问接口
- 避免无脑 getter:不要写
int getStockCount() const { return m_stock_count; },改用 bool hasSufficientStock(const ItemId& item, int qty) const
- 构造函数必须能建立有效状态:如果对象依赖外部配置,就要求传入
const Config&;如果必须延迟初始化,就用 std::optional 或明确的 isValid() 状态检查
- 拷贝和赋值要审慎:多数领域对象应禁用拷贝(
= delete),或实现深拷贝语义;移动操作则视情况开放
public 数据成员;所有字段声明为 private,只在真正需要时提供受控的访问接口int getStockCount() const { return m_stock_count; },改用 bool hasSufficientStock(const ItemId& item, int qty) const
const Config&;如果必须延迟初始化,就用 std::optional 或明确的 isValid() 状态检查= delete),或实现深拷贝语义;移动操作则视情况开放一个典型反例:GlobalState 类里堆了二十个 public 变量,再加个 getInstance() ——这不过是给全局变量套了层 class 外壳,领域语义依然为零。
什么时候该用静态成员,什么时候该用依赖注入
静态成员(如 static UserContext& current())适合真正全局、无状态变化、且生命周期贯穿整个程序的上下文,比如日志器或配置读取器。但它会让单元测试变困难,也隐含了单例约束。
而像 OrderProcessor 这类有状态、需隔离测试、可能多实例并存的对象,必须通过构造函数或方法参数显式传入依赖:
class OrderProcessor {
public:
OrderProcessor(const InventoryManager& inventory,
const PaymentGateway& gateway);
// ...
};
这样既能控制生命周期(比如每个 HTTP 请求创建一个 OrderProcessor 实例),也能在测试时轻松替换模拟依赖。强行把 InventoryManager::instance() 塞进所有地方,等于把耦合写死在实现里。
最难的不是语法层面的封装,而是每次新增一个变量时,停下来问一句:“它属于哪个领域概念?该由谁来拥有它、验证它、保护它?”——这个问题的答案,决定了你的 C++ 代码是在写业务,还是在维护一地鸡毛的全局状态。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











