C++ 改进模块化设计:避免代码变成混乱的毛线团

一、什么是"毛线团"

"毛线团"是一个形象的比喻,描述的是软件工程中的高耦合、低内聚状态:
毛线团=高耦合+低内聚+隐式依赖+循环引用\text{毛线团} = \text{高耦合} + \text{低内聚} + \text{隐式依赖} + \text{循环引用}毛线团=高耦合+低内聚+隐式依赖+循环引用
理想模块=低耦合+高内聚+显式依赖+单向依赖\text{理想模块} = \text{低耦合} + \text{高内聚} + \text{显式依赖} + \text{单向依赖}理想模块=低耦合+高内聚+显式依赖+单向依赖
两种状态的复杂度增长规律截然不同。设系统有 nnn 个模块:
毛线团:连接数=O(n2)(任意模块互连)\text{毛线团:连接数} = O(n^2) \quad \text{(任意模块互连)}毛线团:连接数=O(n2)(任意模块互连)
良好设计:连接数=O(n)(每个模块只依赖少数邻居)\text{良好设计:连接数} = O(n) \quad \text{(每个模块只依赖少数邻居)}良好设计:连接数=O(n)(每个模块只依赖少数邻居)

二、常见误区

误区1:大力出奇迹——God Class(上帝类)

"刚猛风格"的典型表现:一个类承担所有职责,能解决问题就行。

// ✗ 典型的"上帝类":什么都做,什么都知道
// 这种类在 IoT 项目中极为常见,往往从"临时方案"演变而来
class SystemManager {
public:
    // ---- 音频相关 ----
    void initAudio();
    void playSound(const char* file);
    void setVolume(int vol);
    int  getVolume();
    void stopAudio();
    // ---- 网络相关 ----
    void connectWifi(const char* ssid, const char* pwd);
    void disconnectWifi();
    bool isWifiConnected();
    int  sendHttpRequest(const char* url, const char* body);
    // ---- 显示相关 ----
    void initDisplay();
    void drawText(int x, int y, const char* text);
    void drawBitmap(int x, int y, const uint8_t* bmp);
    void refreshScreen();
    // ---- 传感器相关 ----
    float readTemperature();
    float readHumidity();
    int   readADC(uint8_t channel);
    // ---- 存储相关 ----
    bool saveConfig(const char* key, const char* value);
    std::string loadConfig(const char* key);
    bool deleteFile(const char* path);
    // ---- 蓝牙相关 ----
    void startBLEScan();
    void stopBLEScan();
    bool connectBLE(const char* addr);
    // ---- 业务逻辑相关 ----
    void handleUserInput(int keyCode);
    void updateUI();
    void syncToCloud();
    void runAlgorithm();
    // 内部状态:所有模块的数据混在一起
private:
    int     volume_      = 50;
    bool    wifiConn_    = false;
    bool    bleConn_     = false;
    float   temperature_ = 0.0f;
    uint8_t* screenBuf_  = nullptr;
    // ... 几十个成员变量
};
// 问题量化:
// 假设这个类有 50 个方法,修改任何一个都可能影响其他 49 个
// 每次改动的影响面 = O(N),N = 方法数
// 测试难度:无法单独测试某个功能,必须初始化整个 SystemManager

误区2:飞线——全局变量和跨层直接访问

// ✗ 典型的"飞线":模块之间不通过接口,直接访问对方内部
// audio.cpp 中定义的全局变量
int g_audioVolume    = 50;
bool g_audioPlaying  = false;
uint8_t* g_audioBuffer = nullptr;
// network.cpp 直接飞线访问 audio 的内部状态(毫无关系的两个模块!)
extern int g_audioVolume;      // 飞线!
extern bool g_audioPlaying;    // 飞线!
void NetworkManager::onDataReceived(const uint8_t* data, size_t len) {
    // 网络模块直接修改音频状态——这是一种严重的耦合
    if (isCloudCommand(data, len)) {
        g_audioVolume  = parseVolumeCommand(data);  // 网络直接改音量
        g_audioPlaying = true;                       // 网络直接控制播放
    }
}
// display.cpp 也来飞线
extern uint8_t* g_audioBuffer;  // 飞线!显示模块读音频缓冲区?
void DisplayManager::renderWaveform() {
    // 显示模块直接读音频内部缓冲区——极度危险
    // 线程安全?生命周期?完全不考虑
    drawWave(g_audioBuffer, 1024);
}

飞线的危害:
飞线数∝耦合度⇒改动一处,崩溃四方\text{飞线数} \propto \text{耦合度} \Rightarrow \text{改动一处,崩溃四方}飞线数耦合度改动一处,崩溃四方

误区3:循环依赖——死结

// ✗ 循环依赖:A 依赖 B,B 又依赖 A
// 这是"毛线团"最难解开的结
// audio_player.h
#include "ui_controller.h"  // AudioPlayer 依赖 UIController(更新进度条)
class AudioPlayer {
public:
    void play(const char* file);
    void onPlaybackProgress(float progress) {
        ui_.updateProgressBar(progress);  // 调用 UI
    }
private:
    UIController& ui_;  // 直接持有 UI 引用
};
// ui_controller.h
#include "audio_player.h"  // UIController 依赖 AudioPlayer(响应按钮)
class UIController {
public:
    void onPlayButtonClicked() {
        player_.play("music.mp3");  // 调用 AudioPlayer
    }
private:
    AudioPlayer& player_;  // 直接持有 AudioPlayer 引用
};
// 编译结果:循环 include,两个类互相不完整,编译失败
// 即使用前向声明绕过编译,逻辑上仍是死结:
//   AudioPlayer 必须在 UIController 之前初始化
//   UIController 必须在 AudioPlayer 之前初始化
//   → 无解

误区4:日复一日的累积——补丁叠补丁

// ✗ 需求不断增加,每次都在原有代码上"打补丁"
// 最终形成无法理解的"考古代码"
class BLEManager {
public:
    // 第1个月:基础扫描
    void startScan();
    void stopScan();
    // 第2个月:需要过滤,加参数(破坏接口)
    void startScan(const char* filterName);  // 重载,还好
    // 第3个月:需要超时,继续加(接口越来越臃肿)
    void startScan(const char* filterName, int timeoutMs);
    // 第4个月:需要 RSSI 过滤
    void startScan(const char* filterName, int timeoutMs, int minRssi);
    // 第5个月:有人需要不过滤名字但过滤 RSSI,于是:
    void startScanByRssi(int minRssi);  // 新函数!接口爆炸
    // 第6个月:需要回调而不是轮询
    void startScanWithCallback(ScanCallback cb, int timeoutMs, int minRssi);
    // 第7个月:需要连续扫描
    void startContinuousScan(ScanCallback cb, int intervalMs);
    // ... 一年后:18个 startScan 变体,没有人知道该用哪个
    // 每次修改都怕影响其他变体,所以继续"安全地"新增
    // 业务逻辑也混进来了(第3个月的"临时方案")
    void connectIfFound(const char* targetName) {
        // 扫描 + 连接 + 重试 + UI更新,全在这个方法里
        // 200行,无人敢改
    }
private:
    // 标志位炼狱:每个 flag 对应某个历史需求
    bool scanning_          = false;
    bool continuousScan_    = false;
    bool hasNameFilter_     = false;
    bool hasRssiFilter_     = false;
    bool callbackMode_      = false;
    bool autoConnect_       = false;
    // ... 又是十几个 bool
};

三、解决方案:模块化设计的核心原则

3.1 单一职责原则(SRP)——拆开上帝类

// ✓ 将上帝类按职责拆分为独立模块
// 每个类只做一件事,只有一个"变化的理由"
// ---- 音频模块(只管音频)----
class AudioPlayer {
public:
    enum class State { IDLE, PLAYING, PAUSED };
    bool   open(const char* filePath);
    void   play();
    void   pause();
    void   stop();
    void   seekTo(int64_t posMs);
    void   setVolume(int vol);    // 0~100
    int    getVolume()     const;
    int64_t getDurationMs() const;
    int64_t getPositionMs() const;
    State  getState()      const;
private:
    // 只有音频相关的状态
    int     volume_   = 50;
    State   state_    = State::IDLE;
    int64_t posMs_    = 0;
    int64_t durationMs_ = 0;
    // 解码器、硬件句柄等音频专属资源
};
// ---- 网络模块(只管网络)----
class WifiManager {
public:
    struct Config {
        char ssid[64];
        char password[64];
        bool autoReconnect = true;
    };
    bool     connect(const Config& cfg);
    void     disconnect();
    bool     isConnected()   const;
    int      getRssi()       const;
    uint32_t getLocalIP()    const;
private:
    bool    connected_  = false;
    int     rssi_       = -100;
    uint32_t ip_        = 0;
};
// ---- 显示模块(只管显示)----
class DisplayDriver {
public:
    void   clear(uint16_t color = 0x0000);
    void   drawText(int x, int y, const char* text, uint16_t color);
    void   drawRect(int x, int y, int w, int h, uint16_t color);
    void   flush();   // 将缓冲区刷新到屏幕
private:
    uint16_t frameBuf_[240 * 240] = {};  // 只有显示相关数据
};
// 量化收益:
// 拆分前:1个上帝类,50个方法,改动影响面 O(50)
// 拆分后:5个模块,每个约10个方法,改动影响面 O(10)
// 影响面缩减 = 1 - 1/5 = 80%

3.2 依赖倒置原则(DIP)——用接口斩断飞线

核心思想:高层模块不依赖低层模块的具体实现,双方都依赖抽象接口

// ✓ 用纯虚接口替代直接依赖
// ---- 定义抽象接口(稳定,不经常变动)----
// 音频控制接口(抽象)
class IAudioController {
public:
    virtual ~IAudioController() = default;
    virtual void setVolume(int vol)  = 0;
    virtual int  getVolume()   const = 0;
    virtual void play(const char* url) = 0;
    virtual void stop()        = 0;
};
// UI 通知接口(抽象)
class IUINotifier {
public:
    virtual ~IUINotifier() = default;
    virtual void onPlaybackProgress(float progress) = 0;  // 0.0~1.0
    virtual void onPlaybackState(bool isPlaying)    = 0;
    virtual void onVolumeChanged(int vol)           = 0;
};
// ---- 具体实现依赖接口,而非具体类 ----
// AudioPlayer 依赖 IUINotifier 接口,不知道 UIController 的存在
class AudioPlayer : public IAudioController {
public:
    // 通过接口注入 UI 通知器(依赖注入,见下文)
    explicit AudioPlayer(IUINotifier* notifier)
        : notifier_(notifier) {}
    void setVolume(int vol) override {
        volume_ = std::clamp(vol, 0, 100);
        if (notifier_) notifier_->onVolumeChanged(volume_);  // 通过接口通知
    }
    int  getVolume()   const override { return volume_; }
    void play(const char* url) override;
    void stop()        override;
private:
    IUINotifier* notifier_;  // 持有抽象接口,不持有具体类
    int          volume_ = 50;
};
// UIController 依赖 IAudioController 接口,不知道 AudioPlayer 的存在
class UIController : public IUINotifier {
public:
    explicit UIController(IAudioController* audio)
        : audio_(audio) {}
    // 实现 IUINotifier(被 AudioPlayer 回调)
    void onPlaybackProgress(float p) override {
        display_.drawProgressBar(p);  // 只更新 UI,不碰音频内部
    }
    void onPlaybackState(bool playing) override {
        display_.setPlayIcon(playing);
    }
    void onVolumeChanged(int vol) override {
        display_.drawVolumeBar(vol);
    }
    // UI 事件处理
    void onPlayButtonClicked() {
        audio_->play("current_track.mp3");  // 通过接口调用,不知道具体实现
    }
    void onVolumeUpClicked() {
        audio_->setVolume(audio_->getVolume() + 5);
    }
private:
    IAudioController* audio_;    // 持有接口,不持有具体类
    DisplayDriver     display_;
};
// ---- 组装:只在最顶层(main/App)知道具体类型 ----
void appInit() {
    // 1. 创建 UIController(还没有 AudioPlayer,先传 nullptr)
    UIController ui(nullptr);
    // 2. 创建 AudioPlayer,注入 UI 通知接口
    AudioPlayer audio(&ui);
    // 3. 将 AudioPlayer 注入 UIController
    // (实际项目中用依赖注入容器或 App 类统一管理)
    ui.setAudioController(&audio);
    // 此时:
    // AudioPlayer 只知道 IUINotifier,不知道 UIController
    // UIController 只知道 IAudioController,不知道 AudioPlayer
    // 循环依赖被彻底消除!
}

依赖关系的变化:
之前(循环):AudioPlayer⇆UIController\text{之前(循环):} \text{AudioPlayer} \leftrightarrows \text{UIController}之前(循环):AudioPlayerUIController
之后(有向无环):AudioPlayer→IUINotifier←UIController\text{之后(有向无环):} \text{AudioPlayer} \rightarrow \text{IUINotifier} \leftarrow \text{UIController}之后(有向无环):AudioPlayerIUINotifierUIController
AudioPlayer←IAudioController→UIController\text{AudioPlayer} \leftarrow \text{IAudioController} \rightarrow \text{UIController}AudioPlayerIAudioControllerUIController

3.3 依赖注入(DI)——显式声明依赖

// ✓ 依赖注入:把依赖关系从隐式变为显式
// ✗ 反面:在内部偷偷创建依赖(隐式依赖,难以测试)
class AudioPlayer {
public:
    AudioPlayer() {
        // 内部偷偷创建 Logger 和 HardwareCodec——无法替换!
        logger_ = new Logger("/data/audio.log");  // 写死路径
        codec_  = new HardwareCodec();            // 只能用硬件编解码
    }
private:
    Logger*        logger_;
    HardwareCodec* codec_;
};
// ✓ 构造函数注入:依赖从外部传入,可替换、可测试
class AudioPlayer {
public:
    // 依赖通过构造函数显式传入
    // 调用者一看构造函数就知道 AudioPlayer 需要什么
    AudioPlayer(ILogger*  logger,
                ICodec*   codec,
                IHardware* hw)
        : logger_(logger)
        , codec_(codec)
        , hw_(hw) {}
    void play(const char* url) {
        logger_->log(LOG_INFO, "playing: %s", url);  // 通过接口使用
        auto frame = codec_->decode(url);             // 解码
        hw_->output(frame);                           // 输出到硬件
    }
private:
    ILogger*   logger_;  // 接口,不是具体类
    ICodec*    codec_;
    IHardware* hw_;
};
// 生产环境:注入真实实现
AudioPlayer prodPlayer(
    new FileLogger("/data/audio.log"),
    new HardwareH264Codec(),
    new DACHardware()
);
// 单元测试:注入 Mock 实现(无需真实硬件!)
class MockCodec : public ICodec {
public:
    AudioFrame decode(const char* url) override {
        // 返回固定的测试音频帧,不读真实文件
        return AudioFrame::silence(1024);
    }
    int lastDecodeCount = 0;  // 可以记录调用次数,用于断言
};
class MockHardware : public IHardware {
public:
    void output(const AudioFrame& f) override {
        outputFrames.push_back(f);  // 存下来供断言使用
    }
    std::vector<AudioFrame> outputFrames;
};
void testAudioPlayer() {
    MockCodec    mockCodec;
    MockHardware mockHW;
    NullLogger   nullLogger;  // 测试时不写日志
    AudioPlayer player(&nullLogger, &mockCodec, &mockHW);
    player.play("test.mp3");
    // 断言:play 后应该有一帧输出
    assert(mockHW.outputFrames.size() == 1);
}

3.4 观察者模式——解耦事件通知

替代"飞线通知"的标准方案:

// ✓ 观察者模式:事件发布方不知道谁在监听
// 通用事件总线(轻量级,适合 IoT)
template<typename EventType>
class EventBus {
public:
    using Listener = std::function<void(const EventType&)>;
    // 订阅事件(返回 token,用于取消订阅)
    int subscribe(Listener listener) {
        int token = nextToken_++;
        listeners_[token] = std::move(listener);
        return token;
    }
    // 取消订阅
    void unsubscribe(int token) {
        listeners_.erase(token);
    }
    // 发布事件(通知所有订阅者)
    void publish(const EventType& event) {
        // 拷贝一份,防止回调中修改 listeners_
        auto snapshot = listeners_;
        for (auto& [token, listener] : snapshot) {
            listener(event);
        }
    }
private:
    std::unordered_map<int, Listener> listeners_;
    int nextToken_ = 0;
};
// 定义事件类型(简单的数据结构)
struct VolumeChangedEvent {
    int oldVolume;
    int newVolume;
};
struct PlaybackStateEvent {
    enum class State { PLAYING, PAUSED, STOPPED };
    State  state;
    float  progress;  // 0.0~1.0
};
struct NetworkStateEvent {
    bool connected;
    int  rssi;
};
// ---- 使用事件总线解耦模块 ----
// 全局事件总线(或通过 IoC 容器注入)
EventBus<VolumeChangedEvent>   g_volumeBus;
EventBus<PlaybackStateEvent>   g_playbackBus;
EventBus<NetworkStateEvent>    g_networkBus;
// AudioPlayer:只负责发布事件,不知道谁在听
class AudioPlayer {
public:
    void setVolume(int vol) {
        int old = volume_;
        volume_ = std::clamp(vol, 0, 100);
        // 发布事件,不知道谁会响应
        g_volumeBus.publish({old, volume_});
        hw_setVolume(volume_);
    }
    void onPlayProgress(float progress) {
        g_playbackBus.publish({PlaybackStateEvent::State::PLAYING, progress});
    }
private:
    int volume_ = 50;
};
// UIController:订阅感兴趣的事件
class UIController {
public:
    UIController() {
        // 订阅音量变化事件
        volumeToken_ = g_volumeBus.subscribe([this](const VolumeChangedEvent& e) {
            display_.drawVolumeBar(e.newVolume);
            VELA_LOGI("UI", "volume: %d→%d", e.oldVolume, e.newVolume);
        });
        // 订阅播放状态事件
        playbackToken_ = g_playbackBus.subscribe([this](const PlaybackStateEvent& e) {
            display_.updateProgressBar(e.progress);
            display_.setPlayIcon(e.state == PlaybackStateEvent::State::PLAYING);
        });
    }
    ~UIController() {
        // 析构时取消订阅,防止悬空回调
        g_volumeBus.unsubscribe(volumeToken_);
        g_playbackBus.unsubscribe(playbackToken_);
    }
private:
    int volumeToken_;
    int playbackToken_;
    DisplayDriver display_;
};
// 网络模块也可以订阅事件(例如:网络恢复时自动继续播放)
class CloudSyncService {
public:
    CloudSyncService() {
        networkToken_ = g_networkBus.subscribe([this](const NetworkStateEvent& e) {
            if (e.connected) {
                startSync();  // 网络恢复,自动同步
            }
        });
    }
    ~CloudSyncService() { g_networkBus.unsubscribe(networkToken_); }
private:
    void startSync();
    int networkToken_;
};
// 模块间关系:
// AudioPlayer ──publish──→ g_volumeBus ←──subscribe── UIController
//                                      ←──subscribe── VolumeLogger
//                                      ←──subscribe── 任意新模块(开放扩展)
// 
// 添加新订阅者,无需修改 AudioPlayer!满足开闭原则(OCP)

3.5 参数对象模式——解决接口爆炸

针对上文 BLEManager 的 18 个 startScan 变体问题:

// ✓ 用参数对象(Parameter Object)统一接口
// 扫描参数对象:集中所有可配置项,用默认值表达"可选"
struct BLEScanConfig {
    // 过滤条件(默认不过滤)
    const char* nameFilter  = nullptr;  // nullptr = 不过滤名称
    int         minRssi     = -127;     // -127 = 不过滤信号强度
    const char* uuidFilter  = nullptr;  // nullptr = 不过滤 UUID
    // 扫描行为
    int  timeoutMs          = 10000;    // 默认 10 秒超时
    bool continuous         = false;    // 默认单次扫描
    int  intervalMs         = 1000;     // continuous=true 时的间隔
    // 结果处理
    std::function<void(const BLEDevice&)> onFound;    // 发现设备回调
    std::function<void()>                 onTimeout;  // 超时回调
};
// 接口统一为一个,通过参数对象表达所有变体
class BLEManager {
public:
    // 只有一个 startScan!
    bool startScan(const BLEScanConfig& config = {});
    void stopScan();
    bool isScanning() const;
    // 连接也同理
    struct ConnectConfig {
        const char* address;
        int   timeoutMs   = 5000;
        int   retryCount  = 3;
        bool  autoReconn  = true;
        std::function<void(bool success)> onResult;
    };
    bool connect(const ConnectConfig& config);
};
// 使用:非常灵活,但接口只有一个
void scanExample() {
    BLEManager ble;
    // 场景1:最简单的扫描(全用默认值)
    ble.startScan();
    // 场景2:过滤名称
    ble.startScan({
        .nameFilter = "Xiaomi_Watch",
        .timeoutMs  = 5000,
        .onFound    = [](const BLEDevice& dev) {
            VELA_LOGI("BLE", "found: %s", dev.name);
        }
    });
    // 场景3:持续扫描 + RSSI 过滤(原来需要一个新函数,现在直接配置)
    ble.startScan({
        .minRssi    = -70,        // 只要强信号
        .continuous = true,
        .intervalMs = 500,
        .onFound    = [](const BLEDevice& dev) {
            connectIfGoodDevice(dev);
        }
    });
}

3.6 层次化架构——斩断跨层飞线

// ✓ 明确分层,每层只能调用下层接口,绝不跨层调用
// 层次定义(从上到下):
// ┌─────────────────────────────────┐
// │  Layer 4: 应用层(业务逻辑)     │  只依赖 Layer 3
// ├─────────────────────────────────┤
// │  Layer 3: 服务层(跨模块协作)   │  只依赖 Layer 2
// ├─────────────────────────────────┤
// │  Layer 2: 功能层(独立功能模块) │  只依赖 Layer 1
// ├─────────────────────────────────┤
// │  Layer 1: 基础层(硬件抽象/OS)  │  只依赖硬件接口
// └─────────────────────────────────┘
// Layer 1:基础层(HAL,硬件无关接口)
namespace hal {
class IGPIODriver {
public:
    virtual void    setOutput(int pin, bool high) = 0;
    virtual bool    getInput(int pin)       const = 0;
    virtual void    setInterrupt(int pin,
                                 std::function<void()> cb) = 0;
};
class II2CDriver {
public:
    virtual bool write(uint8_t addr, const uint8_t* data, size_t len) = 0;
    virtual bool read (uint8_t addr, uint8_t* buf,  size_t len)       = 0;
};
} // namespace hal
// Layer 2:功能层(独立功能模块,只依赖 HAL 接口)
namespace feature {
class TemperatureSensor {
public:
    explicit TemperatureSensor(hal::II2CDriver& i2c) : i2c_(i2c) {}
    float readCelsius() {
        uint8_t buf[2];
        i2c_.read(SENSOR_ADDR, buf, 2);  // 只依赖 HAL,不知道具体硬件
        return ((buf[0] << 8 | buf[1]) * 0.0625f);
    }
private:
    hal::II2CDriver& i2c_;
    static constexpr uint8_t SENSOR_ADDR = 0x48;
};
class LEDIndicator {
public:
    explicit LEDIndicator(hal::IGPIODriver& gpio, int pin)
        : gpio_(gpio), pin_(pin) {}
    void setColor(bool r, bool g, bool b) {
        gpio_.setOutput(pin_ + 0, r);
        gpio_.setOutput(pin_ + 1, g);
        gpio_.setOutput(pin_ + 2, b);
    }
private:
    hal::IGPIODriver& gpio_;
    int               pin_;
};
} // namespace feature
// Layer 3:服务层(协调多个 feature 模块,实现业务规则)
namespace service {
class EnvironmentMonitor {
public:
    EnvironmentMonitor(feature::TemperatureSensor& temp,
                       feature::LEDIndicator&      led)
        : temp_(temp), led_(led) {}
    // 业务规则:温度超过阈值时亮红灯
    void update() {
        float t = temp_.readCelsius();
        if (t > HIGH_TEMP_THRESHOLD) {
            led_.setColor(true, false, false);  // 红灯警告
            publishAlert(t);
        } else {
            led_.setColor(false, true, false);  // 绿灯正常
        }
    }
private:
    void publishAlert(float temp);
    feature::TemperatureSensor& temp_;
    feature::LEDIndicator&      led_;
    static constexpr float HIGH_TEMP_THRESHOLD = 40.0f;
};
} // namespace service
// Layer 4:应用层(最顶层,组装所有模块,处理用户交互)
namespace app {
class SmartHomeApp {
public:
    SmartHomeApp()
        : i2c_(createI2CDriver())            // 创建 HAL 实现
        , gpio_(createGPIODriver())
        , tempSensor_(*i2c_)                  // 注入 HAL
        , led_(*gpio_, LED_PIN)
        , envMonitor_(tempSensor_, led_)      // 注入 feature
    {}
    void run() {
        while (true) {
            envMonitor_.update();  // 只调用服务层
            sleepMs(1000);
        }
    }
private:
    std::unique_ptr<hal::II2CDriver>   i2c_;
    std::unique_ptr<hal::IGPIODriver>  gpio_;
    feature::TemperatureSensor         tempSensor_;
    feature::LEDIndicator              led_;
    service::EnvironmentMonitor        envMonitor_;
    static constexpr int LED_PIN = 10;
};
} // namespace app

分层架构的依赖方向严格为单向:
app→service→feature→hal→hardware\text{app} \rightarrow \text{service} \rightarrow \text{feature} \rightarrow \text{hal} \rightarrow \text{hardware}appservicefeaturehalhardware
任何反向依赖(如 feature 依赖 app)或跨层依赖(如 app 直接依赖 hal)都是飞线,必须通过接口或事件消除。

四、量化收益:好设计 vs 毛线团

设系统有 nnn 个模块,毛线团与良好设计的对比:
毛线团的修改代价:Cbad=O(n)(改一处,影响大部分模块)\text{毛线团的修改代价:} C_{\text{bad}} = O(n) \quad \text{(改一处,影响大部分模块)}毛线团的修改代价:Cbad=O(n)(改一处,影响大部分模块)
良好设计的修改代价:Cgood=O(1)(改一处,只影响该模块)\text{良好设计的修改代价:} C_{\text{good}} = O(1) \quad \text{(改一处,只影响该模块)}良好设计的修改代价:Cgood=O(1)(改一处,只影响该模块)
随着项目规模增长,两种代价的差距呈线性扩大:
ΔC=Cbad−Cgood=O(n)−O(1)≈O(n)\Delta C = C_{\text{bad}} - C_{\text{good}} = O(n) - O(1) \approx O(n)ΔC=CbadCgood=O(n)O(1)O(n)
n=50n = 50n=50(一个中型 IoT 项目的模块数),修改代价相差约 50 倍。这就是为什么"大力出奇迹"在项目初期看起来很快,但随着代码累积,速度会越来越慢,最终陷入"改哪里都怕崩,加功能比重写还难"的困境。

五、总结:模块化设计的核心准则


问题 根因 解决方案 关键原则
上帝类 职责未拆分 按职责拆分小类 单一职责(SRP)
飞线依赖 直接访问内部状态 接口 + 依赖注入 依赖倒置(DIP)
循环依赖 双向引用 接口 + 事件总线 依赖有向无环
接口爆炸 补丁式新增 参数对象 开闭原则(OCP)
跨层飞线 无层次意识 严格分层 最小知识原则

优雅的代码=高内聚×低耦合×显式依赖×单向分层\text{优雅的代码} = \text{高内聚} \times \text{低耦合} \times \text{显式依赖} \times \text{单向分层}优雅的代码=高内聚×低耦合×显式依赖×单向分层

C++ 模块化设计常见误区深度解析

一、误区全景与危害量化

三种误区本质上是同一个问题的不同表现层次:
设计腐化=模块交叉引用⏟宏观:模块间+接口设计不合理⏟中观:模块边界+类内无关内容多⏟微观:类内部\text{设计腐化} = \underbrace{\text{模块交叉引用}}_{\text{宏观:模块间}} + \underbrace{\text{接口设计不合理}}_{\text{中观:模块边界}} + \underbrace{\text{类内无关内容多}}_{\text{微观:类内部}}设计腐化=宏观:模块间 模块交叉引用+中观:模块边界 接口设计不合理+微观:类内部 类内无关内容多
设系统有 nnn 个模块,每个模块平均有 kkk 个类,每个类平均有 mmm 个方法:
理想依赖数=O(n)实际交叉依赖数=O(n2)腐化倍数=n2n=n\text{理想依赖数} = O(n) \quad \text{实际交叉依赖数} = O(n^2) \quad \text{腐化倍数} = \frac{n^2}{n} = n理想依赖数=O(n)实际交叉依赖数=O(n2)腐化倍数=nn2=n
n=20n = 20n=20 时,交叉引用使依赖复杂度膨胀 20 倍

二、误区一:模块交叉引用

2.1 什么是交叉引用

A 引用 B 的内容,B 同时引用 A 的内容,形成有向环
A→B且B→A⇒环形依赖,无法解耦A \rightarrow B \quad \text{且} \quad B \rightarrow A \quad \Rightarrow \quad \text{环形依赖,无法解耦}ABBA环形依赖,无法解耦

2.2 典型场景:音频与UI的死结

// ✗ 最经典的交叉引用:两个模块互相持有对方的头文件和指针
// ---- audio_player.h ----
#include "ui_controller.h"   // A 包含 B 的头文件
class AudioPlayer {
public:
    explicit AudioPlayer(UIController* ui) : ui_(ui) {}
    void play(const char* url) {
        // 播放时直接调用 UI 更新进度条
        ui_->updateProgressBar(0.0f);
        ui_->setPlayButtonState(true);
        doPlay(url);
    }
    void onProgress(float p) {
        ui_->updateProgressBar(p);   // 直接调用 UI 具体方法
    }
    void onPlayComplete() {
        ui_->setPlayButtonState(false);
        ui_->showCompletionToast();   // 甚至知道 UI 有 Toast!
    }
private:
    UIController* ui_;               // 持有 UI 具体类型
    void doPlay(const char* url);
};
// ---- ui_controller.h ----
#include "audio_player.h"   // B 包含 A 的头文件(死结!)
class UIController {
public:
    explicit UIController(AudioPlayer* player) : player_(player) {}
    void onPlayButtonClicked() {
        player_->play("track.mp3");  // UI 直接调用 AudioPlayer 具体方法
    }
    void onVolumeSliderChanged(int val) {
        player_->setVolume(val);     // UI 直接控制播放器
    }
    void updateProgressBar(float p) { /* 更新进度条 UI */ }
    void setPlayButtonState(bool playing) { /* 更新按钮状态 */ }
    void showCompletionToast() { /* 弹出完成提示 */ }
private:
    AudioPlayer* player_;            // 持有 AudioPlayer 具体类型
};
// 编译问题:
// audio_player.h 包含 ui_controller.h
// ui_controller.h 包含 audio_player.h
// → 循环 include,编译器报错:类型不完整
//
// 即使用前向声明临时绕过编译,逻辑死结仍在:
// AudioPlayer 必须在 UIController 之前构造(因为 UI 要传给 Audio)
// UIController 必须在 AudioPlayer 之前构造(因为 Audio 要传给 UI)
// → 鸡生蛋蛋生鸡,无解

2.3 更隐蔽的交叉引用:通过全局变量

// ✗ 更常见、更难发现的交叉引用:通过全局状态间接相互依赖
// network_manager.cpp
namespace GlobalState {
    bool  isNetworkConnected = false;
    int   networkRssi        = -100;
    char  lastError[128]     = {};
}
// audio_player.cpp
// "只是读一个全局变量而已"——但这是隐式依赖!
extern bool GlobalState::isNetworkConnected;
class AudioPlayer {
    void play(const char* url) {
        // 音频播放器隐式依赖网络状态
        if (strncmp(url, "http", 4) == 0) {
            if (!GlobalState::isNetworkConnected) {  // 读网络全局状态
                showError("网络未连接");
                return;
            }
        }
        doPlay(url);
    }
};
// network_manager.cpp 反过来又读音频状态
extern bool GlobalState::isAudioBuffering;  // 音频定义的全局变量
class NetworkManager {
    void onDataReceived() {
        // 网络管理器隐式依赖音频状态
        if (GlobalState::isAudioBuffering) {  // 读音频全局状态
            prioritizeAudioStream();  // 音频在缓冲时优先处理音频流
        }
    }
};
// 这种交叉依赖的危害:
// 1. 编译时完全看不出来(没有 include 关系)
// 2. 运行时随机崩溃(多线程下的竞争条件)
// 3. 测试时必须同时初始化两个模块才能工作
// 4. 删除任何一个全局变量,另一个模块悄悄失效

2.4 解决方案:接口隔离 + 事件总线

// ✓ 解法核心:引入抽象层,让 A 和 B 都只依赖抽象,互相不知道对方
// ---- 第一步:定义双向通信的抽象接口 ----
// AudioPlayer 需要通知外界的事件(抽象接口,A 定义,不依赖任何模块)
class IAudioEventListener {
public:
    virtual ~IAudioEventListener() = default;
    virtual void onPlaybackStarted()             = 0;
    virtual void onPlaybackProgress(float ratio) = 0;  // 0.0~1.0
    virtual void onPlaybackCompleted()           = 0;
    virtual void onPlaybackError(const char* msg)= 0;
};
// UIController 需要控制音频的接口(抽象接口,B 定义,不依赖任何模块)
class IAudioControl {
public:
    virtual ~IAudioControl() = default;
    virtual void play(const char* url) = 0;
    virtual void pause()               = 0;
    virtual void stop()                = 0;
    virtual void setVolume(int vol)    = 0;
    virtual int  getVolume()     const = 0;
};
// ---- 第二步:AudioPlayer 只依赖 IAudioEventListener(不依赖 UIController)----
// audio_player.h 不再 include ui_controller.h!
class AudioPlayer : public IAudioControl {
public:
    // 注入监听器接口(不是具体的 UIController)
    void setEventListener(IAudioEventListener* listener) {
        listener_ = listener;
    }
    void play(const char* url) override {
        doPlay(url);
        if (listener_) listener_->onPlaybackStarted();  // 通过接口通知
    }
    void pause() override { doPause(); }
    void stop()  override { doStop();  }
    void setVolume(int vol) override {
        volume_ = std::clamp(vol, 0, 100);
    }
    int  getVolume() const override { return volume_; }
private:
    void doPlay(const char* url);
    void doPause();
    void doStop();
    // 进度更新(由解码线程回调)
    void onDecodeProgress(float p) {
        if (listener_) listener_->onPlaybackProgress(p);
    }
    IAudioEventListener* listener_ = nullptr;  // 只持有接口,不知道 UIController
    int                  volume_   = 50;
};
// ---- 第三步:UIController 只依赖 IAudioControl(不依赖 AudioPlayer)----
// ui_controller.h 不再 include audio_player.h!
class UIController : public IAudioEventListener {
public:
    // 注入音频控制接口(不是具体的 AudioPlayer)
    explicit UIController(IAudioControl& audio) : audio_(audio) {}
    // 实现 IAudioEventListener(被 AudioPlayer 回调)
    void onPlaybackStarted() override {
        playBtn_.setIcon(ICON_PAUSE);     // 播放开始,按钮变暂停图标
        progressBar_.setProgress(0.0f);
    }
    void onPlaybackProgress(float ratio) override {
        progressBar_.setProgress(ratio);  // 更新进度条
        timeLabel_.setText(formatTime(ratio));
    }
    void onPlaybackCompleted() override {
        playBtn_.setIcon(ICON_PLAY);      // 播放完成,按钮变播放图标
        toast_.show("播放完成");
    }
    void onPlaybackError(const char* msg) override {
        errorDialog_.show(msg);
    }
    // UI 事件处理(通过接口控制音频)
    void onPlayButtonClicked() {
        audio_.play("current_track.mp3"); // 通过接口,不知道是哪个实现
    }
    void onVolumeChanged(int val) {
        audio_.setVolume(val);
    }
private:
    IAudioControl& audio_;  // 只持有接口,不知道 AudioPlayer
    PlayButton     playBtn_;
    ProgressBar    progressBar_;
    TimeLabel      timeLabel_;
    Toast          toast_;
    ErrorDialog    errorDialog_;
    std::string formatTime(float ratio);
};
// ---- 第四步:在顶层(App)组装,只有这里知道具体类型 ----
class MusicApp {
public:
    MusicApp()
        : player_()
        , ui_(player_)  // UIController 得到 IAudioControl 接口
    {
        // AudioPlayer 得到 IAudioEventListener 接口
        player_.setEventListener(&ui_);
        // 此处是唯一知道二者具体类型的地方
    }
private:
    AudioPlayer    player_;  // 具体类型
    UIController   ui_;      // 具体类型
};
// 依赖图变化:
// 之前(有环):AudioPlayer ←→ UIController
//
// 之后(无环):
//   AudioPlayer ──implements──→ IAudioControl ←──uses── UIController
//   AudioPlayer ──uses──→ IAudioEventListener ←──implements── UIController
//   两个接口由各自的使用方定义,没有任何环

三、误区二:接口设计不合理

3.1 因需求/实现设置接口——接口暴露内部细节

// ✗ 典型反例:接口设计完全跟着实现走,把内部细节暴露出去
class DataSyncService {
public:
    // 以下接口都是"因实现设接口"的典型反面教材:
    // 问题1:暴露内部数据结构(调用方被迫了解 sqlite3)
    sqlite3* getDatabaseHandle();        // 直接把数据库句柄给出去!
    // 调用方拿到 sqlite3*,可以随意执行 SQL,完全绕过服务层
    // 问题2:暴露内部分步实现(强迫调用方按特定顺序调用)
    bool openConnection(const char* host, int port);
    bool authenticate(const char* token);
    bool selectDatabase(const char* dbName);
    bool beginTransaction();
    void syncTableA();          // 必须在 beginTransaction 之后调用!
    void syncTableB();          // 必须在 syncTableA 之后调用!
    void syncTableC();
    bool commitTransaction();   // 必须最后调用!
    // 调用方必须记住这个顺序:
    // open → auth → select → begin → A → B → C → commit
    // 任何一步忘了或顺序错了,结果未定义
    // 问题3:因某个特殊需求临时加的接口,逻辑不自洽
    void syncOnlyIfWifiAndBatteryAbove50Percent();
    // 为什么是 50%?为什么只有 WiFi?这是业务逻辑,不是服务接口!
    // 问题4:返回类型暴露实现细节
    std::vector<std::pair<std::string,
                std::vector<uint8_t>>> getRawSyncQueue();
    // 为什么是 vector<pair<string, vector<uint8_t>>>?
    // 这是内部存储格式,不是外部关心的数据模型
    // 问题5:布尔参数地狱(调用者无法理解参数含义)
    void sync(bool force, bool async, bool retry, bool notify, bool log);
    // 调用处:sync(true, false, true, false, true)
    // 这是什么意思???
};

3.2 接口不合理的类型分类

// 类型一:接口粒度过细(调用方需要组合多步才能完成一件事)
// ✗ 过细
class FileManager {
public:
    FILE*  openFile(const char* path, const char* mode);
    size_t readBytes(FILE* fp, void* buf, size_t n);
    bool   seekFile(FILE* fp, long offset, int whence);
    void   closeFile(FILE* fp);
    // 读一个配置文件需要:open → read → seek → close,4次调用
    // 调用方还得处理 FILE* 的生命周期,极易出错
};
// 类型二:接口粒度过粗(一个接口做太多事)
// ✗ 过粗
class AppManager {
public:
    // 这个接口做了:校验权限 + 准备资源 + 启动进程 + 注册服务 + 更新UI
    // 失败时不知道是哪一步出错
    bool initializeAndStartAppWithPermissionCheckAndRegisterServices(
        const char* appId, const char* userId, bool checkBattery);
};
// 类型三:接口语义模糊(名字和行为不一致)
// ✗ 语义模糊
class NetworkClient {
public:
    // "send" 听起来是发送,实际上还会阻塞等待响应、更新内部缓存
    // 调用方看名字以为是纯发送,被隐藏的行为坑到
    bool send(const uint8_t* data, size_t len);
    // "get" 听起来是纯读取,实际上会触发网络请求(有副作用)
    std::string getDeviceStatus();
};
// 类型四:接口依赖上下文顺序(隐式状态机)
// ✗ 隐式状态机
class BLEConnection {
public:
    void scan();        // 必须第一步
    void connect();     // 必须第二步(scan 之后)
    void discover();    // 必须第三步(connect 之后)
    void subscribe();   // 必须第四步(discover 之后)
    // 接口没有任何机制阻止调用方乱序调用
    // 乱序时行为完全不确定,没有错误提示
};

3.3 解决方案:以使用者为中心设计接口

// ✓ 接口设计核心原则:
// "接口是给调用方用的,不是给实现方用的"
// 从调用方的角度出发,问:我想做什么?而不是:我怎么实现?
// ---- 原则1:最小惊讶原则(函数名即文档)----
class DataSyncService {
public:
    // ✓ 接口语义清晰,一个接口做一件明确的事
    // 调用方只需知道:我想同步数据
    struct SyncOptions {
        bool forceSync    = false;  // 是否强制同步(忽略增量检查)
        bool notifyOnDone = true;   // 完成后是否发通知
        // 注意:没有 wifi/battery 这类业务逻辑,那是调用方的责任
    };
    // 同步结果:明确告知调用方发生了什么
    struct SyncResult {
        enum class Status { SUCCESS, NETWORK_ERROR, AUTH_FAILED, PARTIAL };
        Status  status;
        int     syncedCount;   // 成功同步的条目数
        int     failedCount;   // 失败的条目数
        char    errorMsg[128]; // 失败原因(status != SUCCESS 时有效)
    };
    // ✓ 高层接口:调用方只需一次调用,内部处理所有细节
    SyncResult syncAll(const SyncOptions& opts = {});
    // ✓ 如果确实需要部分同步,用枚举表达,而不是多个方法
    enum class SyncTarget { ALL, USER_DATA, APP_CONFIG, MEDIA };
    SyncResult syncTarget(SyncTarget target, const SyncOptions& opts = {});
    // ✓ 异步版本:调用方不需要知道内部是线程还是协程
    using SyncCallback = std::function<void(SyncResult)>;
    void syncAllAsync(SyncCallback cb, const SyncOptions& opts = {});
};
// ---- 原则2:用强类型替代布尔参数地狱 ----
// ✗ 之前:sync(true, false, true, false, true)  // 完全看不懂
// ✓ 之后:
struct SyncFlags {
    bool force   : 1;  // 是否强制同步
    bool async   : 1;  // 是否异步
    bool retry   : 1;  // 是否重试
    bool notify  : 1;  // 是否通知
    bool logging : 1;  // 是否记录日志
};
// 调用处清晰可读:
service.sync({.force=true, .retry=true, .logging=true});
// ---- 原则3:用状态机显式化隐式顺序 ----
// ✓ 让接口自身约束调用顺序,而不是靠文档和调用方自觉
// BLE 连接的正确设计:状态机 + 工厂方法
class BLEScanner {
public:
    struct ScanResult {
        std::string address;
        std::string name;
        int         rssi;
    };
    using ScanCallback = std::function<void(std::vector<ScanResult>)>;
    // 扫描返回扫描结果,只有从结果才能发起连接
    void scan(int timeoutMs, ScanCallback cb);
};
// BLEConnector 只能从 ScanResult 构造,强制要求先扫描
class BLEConnector {
public:
    // 构造函数需要 ScanResult,没有扫描结果就无法创建连接对象
    // 这在类型系统层面强制了"先扫描再连接"的顺序
    explicit BLEConnector(const BLEScanner::ScanResult& device);
    struct ConnectionHandle {
        // 只有 ConnectionHandle 才能 discover,强制了连接后才能发现
        void discoverServices(std::function<void(ServiceList)> cb);
    };
    // connect 成功后返回 ConnectionHandle,失败返回 nullopt
    // 没有 ConnectionHandle 就无法 discover,类型系统保证顺序
    std::optional<ConnectionHandle> connect(int timeoutMs);
};
// 使用:顺序错误直接编译失败,而不是运行时崩溃
void connectBLE() {
    BLEScanner scanner;
    scanner.scan(5000, [](auto results) {
        if (results.empty()) return;
        BLEConnector connector(results[0]);  // 必须有 ScanResult
        auto handle = connector.connect(3000);
        if (!handle) return;                 // 连接失败,handle 为空
        handle->discoverServices([](auto services) {  // 必须有 handle
            // 处理服务发现结果
        });
        // 不可能在没有 connect 的情况下调用 discoverServices
        // 因为 discoverServices 在 ConnectionHandle 上,
        // 而 ConnectionHandle 只能通过成功的 connect() 获得
    });
}
// ---- 原则4:RAII 管理资源,不暴露底层句柄 ----
// ✗ 暴露 sqlite3*(内部实现细节)
sqlite3* getDatabaseHandle();
// ✓ 用 RAII 对象封装,调用方看不到 sqlite3
class DatabaseQuery {
public:
    // 使用方只需要知道:如何执行查询、如何读取结果
    bool execute(const char* sql);
    bool next();                    // 移动到下一行
    std::string getString(int col); // 读取字符串列
    int         getInt   (int col); // 读取整数列
    // 析构时自动释放 sqlite3_stmt*,调用方完全不知道 sqlite3 的存在
private:
    sqlite3_stmt* stmt_ = nullptr;  // 内部实现细节,对外不可见
};
class DataRepository {
public:
    // 返回查询对象,而不是数据库句柄
    DatabaseQuery query(const char* sql);
    bool execute(const char* sql);  // 无返回结果的语句
private:
    sqlite3* db_ = nullptr;  // 完全隐藏,外部无法触及
};

四、误区三:类内无关内容过多

4.1 一个类里塞入太多无关内容的表现

// ✗ 一个真实项目中常见的"大杂烩"类
// 这个类随时间增长,每次"快速添加"一个功能就塞进来
class SmartWatchApp {
public:
    // ---- 健康监测(第1个月加的)----
    void startHeartRateMonitor();
    void stopHeartRateMonitor();
    float getHeartRate();
    void startSleepTracking();
    void stopSleepTracking();
    SleepReport getSleepReport();
    // ---- 消息通知(第2个月加的)----
    void showNotification(const char* title, const char* body);
    void dismissNotification(int notifId);
    void setNotificationVolume(int vol);
    std::vector<Notification> getPendingNotifications();
    // ---- 运动追踪(第3个月加的)----
    void startWorkout(WorkoutType type);
    void stopWorkout();
    void pauseWorkout();
    float getCaloriesBurned();
    float getDistanceKm();
    int   getStepCount();
    // ---- 支付功能(第4个月加的,完全不相关!)----
    bool loadPaymentCard(const char* cardData);
    bool triggerNFCPayment();
    std::string getCardLastFourDigits();
    bool isPaymentReady();
    // ---- 表盘管理(第5个月加的)----
    void setWatchFace(const char* faceId);
    void updateWatchFaceData();
    std::vector<std::string> getAvailableFaces();
    // ---- 固件升级(第6个月加的)----
    bool checkForUpdate();
    void downloadUpdate(const char* url);
    void applyUpdate();
    float getUpdateProgress();
    // ---- 调试工具(临时加的,忘了删)----
    void dumpMemoryStats();
    void simulateHeartRate(float bpm);  // 测试用!!不该在生产代码
    void injectFakeSteps(int n);        // 测试用!!
    // ---- 数据存储(每个功能都往这里加)----
    void saveToFlash(const char* key, const void* data, size_t len);
    void loadFromFlash(const char* key, void* buf, size_t len);
    void clearAllData();
private:
    // 一锅乱炖的成员变量:
    float        heartRate_          = 0.0f;
    bool         hrMonitoring_       = false;
    SleepData    sleepData_;
    int          notifVolume_        = 50;
    std::vector<Notification> notifications_;
    WorkoutData  workoutData_;
    bool         workoutActive_      = false;
    char         paymentCardData_[256] = {};
    bool         nfcReady_           = false;
    std::string  currentFaceId_;
    uint8_t*     updateBuffer_       = nullptr;
    float        updateProgress_     = 0.0f;
    // ... 还有30多个成员变量
};

内聚度量化。LCOM(缺乏内聚度量)
LCOM=1−∑f∣M(f)∣∣M∣×∣F∣\text{LCOM} = 1 - \frac{\sum_{f} |M(f)|}{|M| \times |F|}LCOM=1M×FfM(f)
其中 ∣M∣|M|M 是方法数,∣F∣|F|F 是字段数,∣M(f)∣|M(f)|M(f) 是访问字段 fff 的方法数。
LCOM→0⇒高内聚(好)LCOM→1⇒低内聚(坏)\text{LCOM} \rightarrow 0 \Rightarrow \text{高内聚(好)} \quad \text{LCOM} \rightarrow 1 \Rightarrow \text{低内聚(坏)}LCOM0高内聚(好)LCOM1低内聚(坏)
上面那个大杂烩类的 LCOM 接近 111,因为大量方法只访问自己那部分字段,彼此毫无关联。

4.2 解决方案:按凝聚度拆分类

// ✓ 按单一职责原则拆分:每个类只负责一个关注点
// ---- 1. 健康监测模块(只管健康数据)----
class HealthMonitor {
public:
    // 心率相关(内聚:都访问 hrData_)
    void  startHeartRate();
    void  stopHeartRate();
    float getHeartRate() const;
    bool  isHeartRateMonitoring() const;
    // 睡眠相关(内聚:都访问 sleepData_)
    void        startSleepTracking();
    void        stopSleepTracking();
    SleepReport getSleepReport() const;
    // 注册数据变化回调(不依赖 UI 具体实现)
    using HeartRateCallback = std::function<void(float bpm)>;
    void setHeartRateCallback(HeartRateCallback cb);
private:
    struct HeartRateData {
        float   currentBpm  = 0.0f;
        bool    monitoring  = false;
        int64_t lastSampleUs = 0;
    } hrData_;
    SleepData sleepData_;
    HeartRateCallback hrCallback_;
};
// ---- 2. 运动追踪模块(只管运动数据)----
class WorkoutTracker {
public:
    enum class Type { RUNNING, WALKING, CYCLING, SWIMMING };
    void  start(Type type);
    void  stop();
    void  pause();
    void  resume();
    bool  isActive()         const;
    float getCalories()      const;
    float getDistanceKm()    const;
    int   getStepCount()     const;
    int64_t getElapsedMs()   const;
    // 步数更新(由计步器硬件驱动调用)
    void onStepDetected();
private:
    struct WorkoutSession {
        Type    type        = Type::WALKING;
        bool    active      = false;
        bool    paused      = false;
        int     steps       = 0;
        float   distanceKm  = 0.0f;
        int64_t startTimeMs = 0;
        int64_t pausedMs    = 0;
    } session_;
    float calculateCalories() const;
};
// ---- 3. 通知管理模块(只管通知)----
class NotificationManager {
public:
    struct Notification {
        int         id;
        std::string title;
        std::string body;
        int64_t     timestampMs;
        bool        dismissed = false;
    };
    int  show(const char* title, const char* body); // 返回通知 ID
    void dismiss(int notifId);
    void dismissAll();
    void setVolume(int vol);
    int  getVolume()   const;
    const std::vector<Notification>& getPending() const;
private:
    std::vector<Notification> notifications_;
    int                       volume_   = 50;
    int                       nextId_   = 1;
};
// ---- 4. 支付模块(只管支付,且有独立安全边界)----
class PaymentManager {
public:
    // 支付相关接口内聚性强(都和支付卡/NFC有关)
    bool loadCard(const char* encryptedCardData); // 加密数据
    bool triggerNFCPayment();
    bool isReady()               const;
    std::string getMaskedNumber() const;  // 返回 **** **** **** 1234
    // 支付安全:只暴露必要信息,不暴露原始卡数据
    bool isCardLoaded() const;
private:
    // 支付数据应在安全存储中,这里只存句柄
    uint32_t    secureCardHandle_ = 0;   // TEE 中的卡数据句柄
    bool        nfcReady_         = false;
    // 明确不提供:getCardRawData(),任何暴露原始卡数据的接口
};
// ---- 5. 固件升级模块(只管OTA)----
class OTAManager {
public:
    enum class State { IDLE, CHECKING, DOWNLOADING, READY, APPLYING, FAILED };
    // 状态变化通知
    using StateCallback = std::function<void(State, float progress)>;
    void setStateCallback(StateCallback cb);
    void checkForUpdate();           // 异步检查
    void startDownload();            // 异步下载
    void applyUpdate();              // 重启并应用
    void cancelDownload();
    State getState()        const;
    float getProgress()     const;  // 0.0~1.0
    std::string getVersion() const; // 可用版本号
private:
    State       state_       = State::IDLE;
    float       progress_    = 0.0f;
    std::string newVersion_;
    uint8_t*    downloadBuf_ = nullptr;
    StateCallback stateCallback_;
};
// ---- 6. 表盘管理模块(只管表盘)----
class WatchFaceManager {
public:
    void setFace(const char* faceId);
    void updateData();   // 刷新表盘显示的数据(时间、步数等)
    std::vector<std::string> getAvailable() const;
    const char* getCurrent() const;
private:
    std::string currentFaceId_;
    std::vector<std::string> availableFaces_;
};
// ---- 7. 数据存储模块(统一存储,避免每个模块自己搞)----
class PersistentStorage {
public:
    bool write(const char* key, const void* data, size_t len);
    bool read (const char* key, void* buf, size_t bufLen, size_t* outLen);
    bool remove(const char* key);
    bool exists(const char* key) const;
    void clearAll();  // 恢复出厂设置时调用
private:
    // 内部用 LittleFS 或 KV 数据库实现
};
// ---- 8. 顶层 App:组装所有模块(只做组装,不加业务逻辑)----
class SmartWatchApp {
public:
    SmartWatchApp()
        : storage_()
        , health_()
        , workout_()
        , notifications_()
        , payment_()
        , ota_()
        , watchFace_()
    {
        // 连接模块间的必要依赖(通过回调或事件总线)
        health_.setHeartRateCallback([this](float bpm) {
            // 心率数据更新时,刷新表盘
            watchFace_.updateData();
        });
        workout_.setStepCallback([this](int totalSteps) {
            watchFace_.updateData();
        });
    }
    // 顶层只暴露高层操作,不暴露子模块的细节
    HealthMonitor&      health()        { return health_;        }
    WorkoutTracker&     workout()       { return workout_;       }
    NotificationManager& notifications(){ return notifications_;  }
    PaymentManager&     payment()       { return payment_;       }
    OTAManager&         ota()           { return ota_;           }
    WatchFaceManager&   watchFace()     { return watchFace_;     }
private:
    PersistentStorage   storage_;
    HealthMonitor       health_;
    WorkoutTracker      workout_;
    NotificationManager notifications_;
    PaymentManager      payment_;
    OTAManager          ota_;
    WatchFaceManager    watchFace_;
};

4.3 测试用代码混入生产代码的专项处理

// ✗ 测试用代码混在生产类里(上文 SmartWatchApp 中的反例)
class HealthMonitor {
public:
    void simulateHeartRate(float bpm);  // 测试用,不该在生产代码!
    void injectFakeSteps(int n);        // 测试用!
};
// ✓ 解法:用接口隔离,测试实现和生产实现完全分开
// 生产代码中:只有真实接口
class IHeartRateSensor {
public:
    virtual ~IHeartRateSensor() = default;
    virtual float readBpm()     = 0;
    virtual bool  isOnline()    = 0;
};
// 生产实现(在 hardware/ 目录)
class HardwareHeartRateSensor : public IHeartRateSensor {
public:
    float readBpm() override {
        return i2c_.readRegFloat(HR_SENSOR_ADDR, REG_BPM);
    }
    bool isOnline() override { return i2c_.probe(HR_SENSOR_ADDR); }
private:
    I2CDriver& i2c_;
    // ...
};
// 测试实现(在 test/ 目录,不进入生产构建)
// #if defined(VELA_TEST_BUILD)  或 CMake 中只在测试目标包含此文件
class MockHeartRateSensor : public IHeartRateSensor {
public:
    // 测试专用:设置模拟数据
    void setSimulatedBpm(float bpm) { simulatedBpm_ = bpm; }
    void setOffline(bool offline)   { offline_       = offline; }
    // 记录调用次数,供 assert 使用
    int readCount = 0;
    float readBpm() override { ++readCount; return simulatedBpm_; }
    bool  isOnline() override { return !offline_; }
private:
    float simulatedBpm_ = 75.0f;
    bool  offline_      = false;
};
// HealthMonitor:依赖接口,不知道是真实硬件还是 Mock
class HealthMonitor {
public:
    explicit HealthMonitor(IHeartRateSensor& sensor)
        : sensor_(sensor) {}
    float getHeartRate() { return sensor_.readBpm(); }  // 通过接口
private:
    IHeartRateSensor& sensor_;  // 接口,不是具体类
    // 生产代码中没有任何 simulate 或 inject 方法!
};
// 测试代码:
void testHealthMonitor() {
    MockHeartRateSensor mockSensor;
    mockSensor.setSimulatedBpm(120.0f);  // 模拟运动心率
    HealthMonitor monitor(mockSensor);
    float bpm = monitor.getHeartRate();
    assert(bpm == 120.0f);
    assert(mockSensor.readCount == 1);  // 确认只读了一次
    // 测试完全不依赖真实硬件!
}

五、三大误区的内在联系

三种误区并非独立存在,往往相互促进,形成恶性循环:

类内无关内容多
      │
      │ 大类变成"什么都能做"的工具箱
      ▼
其他模块直接依赖这个大类
      │
      │ 多个模块都依赖同一个大类
      ▼
模块交叉引用(A依赖大类,大类又依赖A)
      │
      │ 为了解决交叉引用,临时在接口上加方法
      ▼
接口设计不合理(接口随实现走,越来越乱)
      │
      │ 接口乱了,类的职责更模糊
      ▼
类内无关内容更多(循环加剧)

打破这个循环的切入点只有一个:从接口设计开始
好的设计顺序:接口先行⇒明确边界⇒类内聚⇒模块无环\text{好的设计顺序:接口先行} \Rightarrow \text{明确边界} \Rightarrow \text{类内聚} \Rightarrow \text{模块无环}好的设计顺序:接口先行明确边界类内聚模块无环
坏的设计顺序:实现先行⇒边界模糊⇒类膨胀⇒模块交叉\text{坏的设计顺序:实现先行} \Rightarrow \text{边界模糊} \Rightarrow \text{类膨胀} \Rightarrow \text{模块交叉}坏的设计顺序:实现先行边界模糊类膨胀模块交叉
先想清楚"模块对外提供什么能力、需要什么依赖",用接口把边界固化,再去写实现。这就是所谓接口先于实现(Interface-First Design),也是从"大力出奇迹"走向"优雅精巧"的根本路径。

示例1:交叉引用——依赖外溢问题深度解析

一、从图片还原原始代码

根据图片内容,原始代码大致如下:

// ✗ 原始问题代码:WidgetCanvasContextModule 的构造函数
// 引入了两个"非核心"的外部模块依赖
class WidgetCanvasContextModule : public ModuleBase {
public:
    // 构造函数接收了三个参数:
    // jse_context_ref ctx   → JS 引擎上下文(外部模块)
    // WidgetCanvasContext2D* context → Canvas 2D 上下文(核心依赖,合理)
    // DomEntity* canvas     → DOM 实体对象(外部模块)
    WidgetCanvasContextModule(
        jse_context_ref ctx,       // ← 外部模块:JS引擎
        WidgetCanvasContext2D* context,  // ← 核心依赖(合理)
        DomEntity* canvas          // ← 外部模块:DOM实体
    )
        : _ctx(ctx)
        , _context(context)
        , _canvas(canvas)
    {
        // 构造函数体:初始化操作
        // 向外通知消息时使用 _ctx 和 _canvas
    }
    // ... 其他方法
    void notifyCanvasUpdate() {
        // 用 jse_context_ref 向 JS 引擎发消息
        jse_context_call_function(_ctx, ...);
        // 用 DomEntity 触发 DOM 事件
        _canvas->dispatchEvent("update", ...);
    }
private:
    jse_context_ref        _ctx;      // JS引擎上下文
    WidgetCanvasContext2D*  _context;  // Canvas核心对象
    DomEntity*             _canvas;   // DOM实体
};

二、什么是"依赖外溢"

2.1 核心依赖 vs 非核心依赖

WidgetCanvasContextModule 的职责是什么?
→ 管理 Canvas 的 2D 绘图上下文
为此,它真正需要的核心依赖是什么?
→ WidgetCanvasContext2D*(绘图上下文本身)
那 jse_context_ref 和 DomEntity* 是什么?
→ 仅仅是为了"向外通知消息"而引入的
→ 是非核心需求带来的依赖
→ 这就是"依赖外溢"

依赖外溢的定义:
依赖外溢=模块实际依赖数−模块核心职责所需依赖数>0\text{依赖外溢} = \text{模块实际依赖数} - \text{模块核心职责所需依赖数} > 0依赖外溢=模块实际依赖数模块核心职责所需依赖数>0
外溢量=∣{jse_context_ref, DomEntity}∣=2(本例)\text{外溢量} = |\{jse\_context\_ref,\ DomEntity\}| = 2 \quad \text{(本例)}外溢量={jse_context_ref, DomEntity}=2(本例)

2.2 为什么两个依赖会变成网状依赖

直觉上看:只是多了两个参数,有什么大不了?
实际情况:每个外部依赖背后都有一棵依赖树
jse_context_ref
    ├── JSEngine
    │     ├── GarbageCollector
    │     ├── BytecodeCompiler
    │     └── NativeBindingRegistry
    │              ├── PlatformAPI
    │              └── MemoryAllocator
    └── JSRuntime
          ├── EventLoop
          └── ModuleLoader
DomEntity
    ├── EventDispatcher
    │     ├── EventQueue
    │     └── ListenerRegistry
    ├── StyleManager
    │     ├── CSSParser
    │     └── LayoutEngine
    └── RenderTree
          ├── CompositeLayer
          └── DisplayList
WidgetCanvasContextModule 原本只需要:
    WidgetCanvasContext2D
        └── RenderBuffer(合理,这是 Canvas 核心)
引入两个"小依赖"后,实际传递依赖变成:
    WidgetCanvasContextModule
        ├── WidgetCanvasContext2D(核心,合理)
        ├── JSEngine + JSRuntime + GC + Compiler + ...(外溢!)
        └── EventDispatcher + StyleManager + RenderTree + ...(外溢!)

传递依赖的数量爆炸:
D传递=D直接+∑d∈D直接D传递(d)D_{\text{传递}} = D_{\text{直接}} + \sum_{d \in D_{\text{直接}}} D_{\text{传递}}(d)D传递=D直接+dD直接D传递(d)
D传递(Canvas)≈1+8+6=15(粗略估计)D_{\text{传递}}(\text{Canvas}) \approx 1 + 8 + 6 = 15 \quad \text{(粗略估计)}D传递(Canvas)1+8+6=15(粗略估计)
原本只有 D=1D = 1D=1 的干净依赖,因为两个非核心引入,变成了 D=15D = 15D=15 的网状依赖,复杂度膨胀 15 倍

三、依赖外溢的完整危害展示

// 展示依赖外溢如何逐步把代码变成毛线团
// 第1个月:WidgetCanvasContextModule 正常工作
// 初始依赖链:Canvas → WidgetCanvasContext2D
// 第2个月:需要向 JS 层回调,工程师"快速"加入 jse_context_ref
// 依赖链:Canvas → WidgetCanvasContext2D
//                → jse_context_ref → JSEngine → ...
// 第3个月:需要触发 DOM 事件,工程师"快速"加入 DomEntity
// 依赖链:Canvas → WidgetCanvasContext2D
//                → jse_context_ref → JSEngine → ...
//                → DomEntity       → EventDispatcher → ...
// 第4个月:Canvas 需要响应屏幕旋转,工程师加入 ScreenManager
// Canvas 的头文件现在 include 了:
//   canvas_context_2d.h
//   jse_context.h      ← JS引擎头文件(1200行)
//   dom_entity.h       ← DOM头文件(800行)
//   screen_manager.h   ← 屏幕管理(600行)
// 第5个月:其他模块需要使用 Canvas
// NetworkImageLoader 需要把网络图片绘制到 Canvas
class NetworkImageLoader {
    // 为了创建 WidgetCanvasContextModule,
    // NetworkImageLoader 被迫也依赖:
    //   jse_context_ref(它根本不需要!)
    //   DomEntity(它也不需要!)
    void drawToCanvas(WidgetCanvasContextModule* canvas,
                      jse_context_ref ctx,    // ← 被迫传入,毫无意义
                      DomEntity* dom) {       // ← 被迫传入,毫无意义
        // ...
    }
};
// 第6个月:单元测试 Canvas
// 为了测试 Canvas 的绘图功能,必须:
//   1. 初始化完整的 JS 引擎(jse_context_ref)
//   2. 构造一个 DomEntity(需要完整的 DOM 树)
//   3. 才能创建 WidgetCanvasContextModule
// 原本只需要一个 RenderBuffer,现在需要整个前端运行时!
void testCanvasDrawRect() {
    // ✗ 为了测试一个 drawRect,要初始化这些:
    JSEngine     jsEngine;                    // 几百毫秒启动时间
    jse_context_ref ctx = jsEngine.createContext();
    DOMTree      domTree;
    DomEntity*   entity = domTree.createElement("canvas");
    WidgetCanvasContext2D context;
    // 终于可以创建被测对象了...
    WidgetCanvasContextModule module(ctx, &context, entity);
    module.drawRect(0, 0, 100, 100);          // 测这一行,前面铺垫了10行
    // 测试 Canvas 的绘图,却要依赖 JS 引擎,荒谬!
}

四、问题根源分析

// 追问:为什么工程师会这样设计?
// 通常是这样的思维过程:
// "Canvas 需要在绘图完成后通知 JS 层"
// → "通知 JS 层需要 jse_context_ref"
// → "那就把 jse_context_ref 传进来吧,简单直接"
// "Canvas 需要触发 DOM 的 onchange 事件"
// → "触发事件需要 DomEntity"
// → "那就把 DomEntity 也传进来吧"
// 这种思维模式的问题:
// 把"如何实现通知"(实现细节)混入了"谁负责通知"(职责划分)
// Canvas 的职责是绘图,不是管理JS回调和DOM事件
// "向外通知"这个需求本身没错,错在让 Canvas 自己持有通知所需的全部资源
// 正确的思维应该是:
// "Canvas 需要通知外界"
// → "通知是一种抽象能力,用接口表达"
// → "Canvas 持有通知接口,不持有具体的 JS 引擎或 DOM 对象"

职责边界原则:
Canvas 的职责=管理2D绘图状态+执行绘图命令\text{Canvas 的职责} = \text{管理2D绘图状态} + \text{执行绘图命令}Canvas 的职责=管理2D绘图状态+执行绘图命令
Canvas 不应该的职责=管理JS回调+触发DOM事件\text{Canvas 不应该的职责} = \text{管理JS回调} + \text{触发DOM事件}Canvas 不应该的职责=管理JS回调+触发DOM事件
违反此原则⇒依赖外溢⇒网状依赖\text{违反此原则} \Rightarrow \text{依赖外溢} \Rightarrow \text{网状依赖}违反此原则依赖外溢网状依赖

五、解决方案:三步消除依赖外溢

5.1 第一步:识别"向外通知"的本质

// "向外通知"是一种抽象行为,与通知的具体机制无关
// Canvas 只需要知道:我有事情要告诉外界
// 至于外界是 JS 引擎、DOM 系统、还是其他,Canvas 不关心
// 抽象出通知接口(Canvas 自己定义,不依赖任何外部模块)
class ICanvasEventNotifier {
public:
    virtual ~ICanvasEventNotifier() = default;
    // Canvas 关心的事件(从 Canvas 的视角定义,而非从 JS/DOM 的视角)
    virtual void onCanvasUpdated()                           = 0;
    virtual void onDrawCallCompleted(const char* operation)  = 0;
    virtual void onCanvasError(int code, const char* msg)    = 0;
    virtual void onFrameReady(const uint8_t* pixels,
                               int width, int height)        = 0;
};
// 注意:这个接口完全不提 jse_context_ref 或 DomEntity
// Canvas 只说"我更新了",不关心谁在听、怎么处理

5.2 第二步:重构 Canvas 模块,只依赖核心 + 抽象接口

// ✓ 重构后的 WidgetCanvasContextModule
// 依赖关系变成:
//   WidgetCanvasContext2D(核心绘图,必须)
//   ICanvasEventNotifier(抽象通知,必须)
// 完全消除了 jse_context_ref 和 DomEntity 的直接依赖
class WidgetCanvasContextModule : public ModuleBase {
public:
    // 构造函数:只接受核心依赖 + 抽象接口
    WidgetCanvasContextModule(
        WidgetCanvasContext2D* context,     // 核心依赖:绘图上下文
        ICanvasEventNotifier*  notifier     // 抽象接口:通知能力
    )
        : _context(context)
        , _notifier(notifier)
    {
        // 不再需要 jse_context_ref 和 DomEntity!
    }
    // ---- 核心绘图 API ----
    void clearRect(float x, float y, float w, float h) {
        _context->clearRect(x, y, w, h);
        // 绘图完成后通过接口通知,不知道通知的具体实现
        if (_notifier) _notifier->onDrawCallCompleted("clearRect");
    }
    void fillRect(float x, float y, float w, float h) {
        _context->fillRect(x, y, w, h);
        if (_notifier) _notifier->onDrawCallCompleted("fillRect");
    }
    void drawImage(const ImageData& img, float x, float y) {
        _context->drawImage(img, x, y);
        if (_notifier) _notifier->onDrawCallCompleted("drawImage");
    }
    // 提交当前帧(渲染完一帧后通知外界)
    void commit() {
        auto pixels = _context->getPixelBuffer();
        _context->flush();
        // 通过接口通知帧就绪,不知道谁在处理这个帧
        if (_notifier) {
            _notifier->onFrameReady(
                pixels.data,
                pixels.width,
                pixels.height
            );
        }
    }
    void setFillColor(uint32_t rgba) { _context->setFillColor(rgba); }
    void setStrokeColor(uint32_t rgba){ _context->setStrokeColor(rgba); }
    void setLineWidth(float w)        { _context->setLineWidth(w); }
private:
    WidgetCanvasContext2D* _context;   // 只持有核心对象
    ICanvasEventNotifier*  _notifier;  // 只持有抽象接口
    // ✗ 以下两个成员彻底消失:
    // jse_context_ref _ctx;     ← 删除!
    // DomEntity*      _canvas;  ← 删除!
};

5.3 第三步:在外部实现具体通知逻辑

// ✓ JS 引擎侧的通知实现(在 JS 桥接层,而非 Canvas 层)
// 这个类知道 jse_context_ref,但 Canvas 不知道它的存在
class JSCanvasNotifier : public ICanvasEventNotifier {
public:
    // 构造时注入 JS 引擎上下文(这里持有 jse_context_ref 是合理的)
    explicit JSCanvasNotifier(jse_context_ref ctx,
                               const char* callbackName)
        : _ctx(ctx)
        , _callbackName(callbackName)
    {}
    void onCanvasUpdated() override {
        // 在这里才使用 jse_context_ref 调用 JS 回调
        jse_value_t callback = jse_get_named_property(
            _ctx, jse_get_global(_ctx), _callbackName);
        if (jse_value_is_function(_ctx, callback)) {
            jse_call_function(_ctx, callback,
                              jse_make_undefined(_ctx), 0, nullptr);
        }
        jse_free_value(_ctx, callback);
    }
    void onDrawCallCompleted(const char* operation) override {
        // 可以记录性能数据,回调 JS 的 onDrawCall 钩子
        jse_value_t args[1] = {
            jse_create_string(_ctx, operation)
        };
        // ... 调用 JS 钩子
        jse_free_value(_ctx, args[0]);
    }
    void onCanvasError(int code, const char* msg) override {
        // 向 JS 抛出错误事件
        jse_value_t errObj = jse_create_object(_ctx);
        jse_set_property(_ctx, errObj, "code",
                         jse_create_number(_ctx, code));
        jse_set_property(_ctx, errObj, "message",
                         jse_create_string(_ctx, msg));
        // 触发 JS error 回调
        // ...
        jse_free_value(_ctx, errObj);
    }
    void onFrameReady(const uint8_t* pixels,
                      int width, int height) override {
        // 将像素数据传回 JS(通过 ArrayBuffer)
        jse_value_t buffer = jse_create_array_buffer(
            _ctx, pixels, width * height * 4);
        // 调用 JS 的 onframe 回调
        // ...
        jse_free_value(_ctx, buffer);
    }
private:
    jse_context_ref _ctx;           // JS引擎上下文:在这里持有是合理的
    const char*     _callbackName;
};
// ✓ DOM 侧的通知实现(在 DOM 桥接层,而非 Canvas 层)
class DOMCanvasNotifier : public ICanvasEventNotifier {
public:
    explicit DOMCanvasNotifier(DomEntity* canvasElement)
        : _canvasElement(canvasElement)
    {}
    void onCanvasUpdated() override {
        // 在这里才使用 DomEntity 触发 DOM 事件
        DomEvent event("canvasupdate");
        event.bubbles    = true;
        event.cancelable = false;
        _canvasElement->dispatchEvent(event);
    }
    void onDrawCallCompleted(const char* operation) override {
        // 可以触发 DOM 的 draw 事件
        DomEvent event("draw");
        event.detail = operation;
        _canvasElement->dispatchEvent(event);
    }
    void onCanvasError(int code, const char* msg) override {
        DomEvent errEvent("error");
        errEvent.detail = msg;
        _canvasElement->dispatchEvent(errEvent);
    }
    void onFrameReady(const uint8_t* pixels,
                      int w, int h) override {
        // 通知 DOM 更新显示
        _canvasElement->markDirty();
        _canvasElement->scheduleRender();
    }
private:
    DomEntity* _canvasElement;  // DomEntity:在这里持有是合理的
};
// ✓ 可以同时通知多个目标(组合模式)
class CompositeCanvasNotifier : public ICanvasEventNotifier {
public:
    void addNotifier(ICanvasEventNotifier* n) {
        _notifiers.push_back(n);
    }
    void onCanvasUpdated() override {
        for (auto* n : _notifiers) n->onCanvasUpdated();
    }
    void onDrawCallCompleted(const char* op) override {
        for (auto* n : _notifiers) n->onDrawCallCompleted(op);
    }
    void onCanvasError(int code, const char* msg) override {
        for (auto* n : _notifiers) n->onCanvasError(code, msg);
    }
    void onFrameReady(const uint8_t* px, int w, int h) override {
        for (auto* n : _notifiers) n->onFrameReady(px, w, h);
    }
private:
    std::vector<ICanvasEventNotifier*> _notifiers;
};

5.4 第四步:在组装层(顶层)连接所有模块

// ✓ 只有在最顶层,才把所有具体类型组装在一起
// Canvas 模块本身完全不知道 JS 引擎或 DOM 的存在
class CanvasWidget {
public:
    CanvasWidget(jse_context_ref jsCtx, DomEntity* domEntity) {
        // 创建核心绘图上下文(Canvas 的真正核心)
        _context = std::make_unique<WidgetCanvasContext2D>(
            width_, height_
        );
        // 创建具体通知实现(组装层知道具体类型,Canvas 不知道)
        _jsNotifier  = std::make_unique<JSCanvasNotifier>(
            jsCtx, "onCanvasFrame"
        );
        _domNotifier = std::make_unique<DOMCanvasNotifier>(domEntity);
        // 用组合通知器同时通知 JS 和 DOM
        _compositeNotifier = std::make_unique<CompositeCanvasNotifier>();
        _compositeNotifier->addNotifier(_jsNotifier.get());
        _compositeNotifier->addNotifier(_domNotifier.get());
        // 创建 Canvas 模块(只传入核心依赖 + 抽象接口)
        // WidgetCanvasContextModule 完全不知道 jse_context_ref 的存在!
        _canvasModule = std::make_unique<WidgetCanvasContextModule>(
            _context.get(),
            _compositeNotifier.get()  // 只传接口
        );
    }
    WidgetCanvasContextModule* getContext() {
        return _canvasModule.get();
    }
private:
    int width_  = 300;
    int height_ = 150;
    std::unique_ptr<WidgetCanvasContext2D>     _context;
    std::unique_ptr<JSCanvasNotifier>          _jsNotifier;
    std::unique_ptr<DOMCanvasNotifier>         _domNotifier;
    std::unique_ptr<CompositeCanvasNotifier>   _compositeNotifier;
    std::unique_ptr<WidgetCanvasContextModule> _canvasModule;
};

六、重构前后的依赖对比

重构前(依赖外溢)

WidgetCanvasContextModule
    ├── WidgetCanvasContext2D   ← 核心,合理
    ├── jse_context_ref         ← 外溢!
    │     ├── JSEngine
    │     │     ├── GarbageCollector
    │     │     ├── BytecodeCompiler
    │     │     └── NativeBindingRegistry ── PlatformAPI
    │     └── JSRuntime
    │           ├── EventLoop
    │           └── ModuleLoader
    └── DomEntity               ← 外溢!
          ├── EventDispatcher ── EventQueue
          │                   └── ListenerRegistry
          ├── StyleManager    ── CSSParser
          │                   └── LayoutEngine
          └── RenderTree      ── CompositeLayer
直接依赖:3个
传递依赖:约15个

重构后(依赖收敛)

WidgetCanvasContextModule
    ├── WidgetCanvasContext2D   ← 核心,合理
    └── ICanvasEventNotifier    ← 抽象接口,稳定
JSCanvasNotifier(实现 ICanvasEventNotifier)
    └── jse_context_ref         ← 在正确的位置持有
DOMCanvasNotifier(实现 ICanvasEventNotifier)
    └── DomEntity               ← 在正确的位置持有
WidgetCanvasContextModule 的直接依赖:2个
WidgetCanvasContextModule 的传递依赖:1个(WidgetCanvasContext2D 的内部)

依赖复杂度的变化:
D重构前(Canvas)≈15D重构后(Canvas)≈2D_{\text{重构前}}(\text{Canvas}) \approx 15 \quad D_{\text{重构后}}(\text{Canvas}) \approx 2D重构前(Canvas)15D重构后(Canvas)2
复杂度下降=1−215≈87%\text{复杂度下降} = 1 - \frac{2}{15} \approx 87\%复杂度下降=115287%

七、单元测试验证重构效果

// ✓ 重构后,Canvas 测试完全不需要 JS 引擎或 DOM
// Mock 通知器:测试专用,零外部依赖
class MockCanvasNotifier : public ICanvasEventNotifier {
public:
    // 记录被调用的事件,供断言使用
    std::vector<std::string> drawCalls;
    int frameReadyCount    = 0;
    int errorCount         = 0;
    bool canvasUpdated     = false;
    void onCanvasUpdated() override {
        canvasUpdated = true;
    }
    void onDrawCallCompleted(const char* op) override {
        drawCalls.emplace_back(op);
    }
    void onCanvasError(int code, const char* msg) override {
        ++errorCount;
    }
    void onFrameReady(const uint8_t*, int, int) override {
        ++frameReadyCount;
    }
};
// 测试 Canvas 的核心绘图功能
void testCanvasFillRect() {
    // 准备:只需要最小依赖
    WidgetCanvasContext2D context(100, 100);  // 100x100 的离屏 Canvas
    MockCanvasNotifier    mockNotifier;       // 零依赖的 Mock
    // 创建被测对象(不需要 JSEngine!不需要 DomEntity!)
    WidgetCanvasContextModule canvas(&context, &mockNotifier);
    // 执行测试
    canvas.setFillColor(0xFF0000FF);  // 红色
    canvas.fillRect(10, 10, 50, 50);
    // 断言
    assert(mockNotifier.drawCalls.size() == 1);
    assert(mockNotifier.drawCalls[0] == "fillRect");
    // 验证像素(直接从 context 读,不需要通过 JS 或 DOM)
    auto pixel = context.getPixel(30, 30);  // 矩形中心点
    assert(pixel.r == 0xFF);
    assert(pixel.g == 0x00);
    assert(pixel.b == 0x00);
    // 测试通知机制
    canvas.commit();
    assert(mockNotifier.frameReadyCount == 1);
}
void testCanvasDrawSequence() {
    WidgetCanvasContext2D context(200, 200);
    MockCanvasNotifier    mockNotifier;
    WidgetCanvasContextModule canvas(&context, &mockNotifier);
    // 执行一系列绘图命令
    canvas.clearRect(0, 0, 200, 200);
    canvas.setFillColor(0x0000FFFF);
    canvas.fillRect(0, 0, 100, 100);
    canvas.setFillColor(0x00FF00FF);
    canvas.fillRect(100, 100, 100, 100);
    // 验证绘图命令序列
    assert(mockNotifier.drawCalls.size() == 3);
    assert(mockNotifier.drawCalls[0] == "clearRect");
    assert(mockNotifier.drawCalls[1] == "fillRect");
    assert(mockNotifier.drawCalls[2] == "fillRect");
}
// 整个测试套件:
// - 不需要 JS 引擎(节省几百毫秒启动时间)
// - 不需要 DOM 树(节省大量初始化代码)
// - 可以在 CI 上纯 CPU 环境运行(无需图形硬件)
// - 测试速度:毫秒级 vs 原来的秒级

八、依赖外溢的识别清单

在代码审查中,以下信号提示可能存在依赖外溢:

// 信号1:构造函数参数中出现与类名不相关的类型
WidgetCanvasContextModule(
    jse_context_ref ctx,    // ← "Canvas" 里出现 "jse"?警惕!
    DomEntity* canvas       // ← "Canvas" 里出现 "Dom"?警惕!
);
// 信号2:头文件 include 了与类职责无关的头文件
// canvas_context_module.h 中出现:
#include "jse/jse_context.h"    // Canvas 为何需要 JS 引擎头文件?
#include "dom/dom_entity.h"     // Canvas 为何需要 DOM 头文件?
// 信号3:成员变量中有"为了通知用"的外部对象
class SomeModule {
    ExternalObject* obj_;  // 注释写着"仅用于向外通知"
    // ← 这正是依赖外溢的典型症状!
};
// 信号4:单元测试需要大量 setup 才能创建被测对象
void testSomeSimpleFeature() {
    // 初始化了10个对象才能测一个功能 ← 强烈信号!
    JSEngine js; DOMTree dom; NetworkStack net; ...
    SomeModule module(js, dom, net, ...);
    module.doSimpleThing();
}

判断一个依赖是否"外溢"的核心问题只有一个:
如果去掉这个依赖,这个类还能完成它的核心职责吗?\boxed{\text{如果去掉这个依赖,这个类还能完成它的核心职责吗?}}如果去掉这个依赖,这个类还能完成它的核心职责吗?
能⇒这个依赖是外溢的,应该通过抽象接口替代\text{能} \Rightarrow \text{这个依赖是外溢的,应该通过抽象接口替代}这个依赖是外溢的,应该通过抽象接口替代
不能⇒这个依赖是核心的,直接持有是合理的\text{不能} \Rightarrow \text{这个依赖是核心的,直接持有是合理的}不能这个依赖是核心的,直接持有是合理的
对于本例:去掉 jse_context_refDomEntity,Canvas 依然能绘图。因此它们是外溢依赖,用 ICanvasEventNotifier 接口替代,是正确的解法。

解决经验:分层设计——上层依赖下层,下层不依赖上层

一、两张图的对比:问题 vs 解决

图一:问题状态——依赖外溢

Widget

Application

DomEntity

jse_context_ref

WidgetCanvasContextModule

问题核心:下层的 WidgetCanvasContextModule 反向依赖了上层的 ApplicationDomEntity,以及完全无关层的 jse_context_ref

图二:解决状态——分层 + 接口隔离

解耦实现层

Widget

Application

DomEntity

jse_context_ref

WidgetCanvasContextModule

WidgetCanvasContext\nClient

WidgetCanvasContext\nClientImpl

解决核心:引入 WidgetCanvasContextClient 接口,让 Canvas 模块只依赖抽象,外溢依赖转移到专门的 ClientImpl 层。

二、分层设计的核心原则

2.1 层次定义

正确的分层方向(依赖只能向下):
┌─────────────────────────────────────┐  ← 最高层
│          Application                │     知道一切
│  (知道 DomEntity、Widget、Impl)   │
└──────────────┬──────────────────────┘
               │ 依赖(向下)
┌──────────────▼──────────────────────┐
│           DomEntity                 │     知道 Widget
│  (知道 Widget,不知道 Application) │
└──────────────┬──────────────────────┘
               │ 依赖(向下)
┌──────────────▼──────────────────────┐
│             Widget                  │     只知道接口
│  WidgetCanvasContextModule          │
│  (只依赖 WidgetCanvasContextClient)│
└──────────────┬──────────────────────┘
               │ 依赖(接口,稳定)
┌──────────────▼──────────────────────┐
│    WidgetCanvasContextClient        │  ← 抽象接口层
│  (纯抽象,无任何具体依赖)          │     最稳定
└─────────────────────────────────────┘

分层的数学约束:设层级编号从下到上为 L1<L2<⋯<LnL_1 < L_2 < \cdots < L_nL1<L2<<Ln,合法依赖方向为:
Li→Lj当且仅当i>j(上层依赖下层)L_i \rightarrow L_j \quad \text{当且仅当} \quad i > j \quad \text{(上层依赖下层)}LiLj当且仅当i>j(上层依赖下层)
Li→Lj当i<j⇒违规!下层依赖上层L_i \rightarrow L_j \quad \text{当} \quad i < j \quad \Rightarrow \text{违规!下层依赖上层}LiLji<j违规!下层依赖上层
本例的违规:
LWidget→LApplication⇒下层依赖上层,违规L_{\text{Widget}} \rightarrow L_{\text{Application}} \quad \Rightarrow \quad \text{下层依赖上层,违规}LWidgetLApplication下层依赖上层,违规
LWidget→LDomEntity⇒下层依赖上层,违规L_{\text{Widget}} \rightarrow L_{\text{DomEntity}} \quad \Rightarrow \quad \text{下层依赖上层,违规}LWidgetLDomEntity下层依赖上层,违规

三、解决方案的完整 C++ 实现

3.1 定义抽象接口(最底层,最稳定)

// widget_canvas_context_client.h
// 这个文件是整个解耦方案的核心
// 它处于最底层:不依赖任何具体模块,只定义纯抽象行为
// Canvas 模块需要"向外通知"的所有能力,都在这里定义
#pragma once
// 前向声明(连具体类型都不引入)
struct CanvasPixelBuffer {
    const uint8_t* data;
    int            width;
    int            height;
    int            stride;  // 每行字节数
};
// WidgetCanvasContextClient:Canvas 模块对外通信的唯一出口
// 命名规范:Client 表示"Canvas 是服务提供方,这是服务消费方的接口"
class WidgetCanvasContextClient {
public:
    virtual ~WidgetCanvasContextClient() = default;
    // ---- Canvas 状态变化通知 ----
    // 画布内容已更新(用于触发重绘)
    virtual void onCanvasInvalidated() = 0;
    // 一帧绘制完成,像素数据就绪
    virtual void onFrameReady(const CanvasPixelBuffer& frame) = 0;
    // ---- 错误通知 ----
    // Canvas 发生错误(错误码 + 描述)
    virtual void onError(int code, const char* message) = 0;
    // ---- JS 脚本事件通知 ----
    // (Canvas 不知道是 JS 在消费,只知道"有脚本事件需要触发")
    // 绘图操作完成,通知脚本层
    virtual void onDrawCallDispatched(const char* opName,
                                       int64_t     durationUs) = 0;
    // Canvas 尺寸变化
    virtual void onSizeChanged(int newWidth, int newHeight) = 0;
};

3.2 重构 Canvas 模块(只依赖接口,彻底干净)

// widget_canvas_context_module.h
// Canvas 模块重构后的样子:
// 依赖:WidgetCanvasContext2D(核心绘图)+ WidgetCanvasContextClient(通知接口)
// 完全没有 jse_context_ref、DomEntity、Application 的痕迹
#pragma once
#include "widget_canvas_context_2d.h"
#include "widget_canvas_context_client.h"  // 只 include 接口,不 include 任何上层模块
// ✗ 以下 include 全部删除:
// #include "jse/jse_context.h"     ← 删除
// #include "dom/dom_entity.h"      ← 删除
// #include "app/application.h"     ← 删除
class WidgetCanvasContextModule : public ModuleBase {
public:
    // 构造函数:只接受核心依赖 + 抽象接口
    // 参数列表一眼就能看出:Canvas 需要什么,非常清晰
    WidgetCanvasContextModule(
        WidgetCanvasContext2D*    context,   // 核心:2D绘图能力
        WidgetCanvasContextClient* client    // 抽象:对外通知能力
    )
        : _context(context)
        , _client(client)
    {
        // 构造函数体简洁,无任何奇怪的初始化
    }
    // ---- 2D 绘图 API(Canvas 的核心职责)----
    void clearRect(float x, float y, float w, float h) {
        int64_t t0 = getMonotonicUs();
        _context->clearRect(x, y, w, h);
        // 通过接口通知,不知道谁在处理
        notifyDrawCall("clearRect", getMonotonicUs() - t0);
    }
    void fillRect(float x, float y, float w, float h) {
        int64_t t0 = getMonotonicUs();
        _context->fillRect(x, y, w, h);
        notifyDrawCall("fillRect", getMonotonicUs() - t0);
    }
    void drawImage(const ImageData& img, float dx, float dy) {
        int64_t t0 = getMonotonicUs();
        _context->drawImage(img, dx, dy);
        notifyDrawCall("drawImage", getMonotonicUs() - t0);
    }
    void strokeRect(float x, float y, float w, float h) {
        int64_t t0 = getMonotonicUs();
        _context->strokeRect(x, y, w, h);
        notifyDrawCall("strokeRect", getMonotonicUs() - t0);
    }
    // 样式设置
    void setFillColor(uint32_t rgba)   { _context->setFillColor(rgba);   }
    void setStrokeColor(uint32_t rgba) { _context->setStrokeColor(rgba); }
    void setLineWidth(float w)         { _context->setLineWidth(w);      }
    void setGlobalAlpha(float alpha)   { _context->setGlobalAlpha(alpha);}
    // 提交当前帧到显示
    void commit() {
        // 获取当前帧像素数据
        CanvasPixelBuffer frame = _context->getPixelBuffer();
        _context->flush();
        // 通过接口通知帧就绪,不知道是谁在接收(JS?DOM?硬件?)
        if (_client) _client->onFrameReady(frame);
    }
    // 尺寸变化(外部调用)
    void resize(int newWidth, int newHeight) {
        _context->resize(newWidth, newHeight);
        if (_client) _client->onSizeChanged(newWidth, newHeight);
    }
private:
    // 成员变量:只有两个,极度干净
    WidgetCanvasContext2D*    _context;  // 核心绘图对象
    WidgetCanvasContextClient* _client;  // 抽象通知接口
    // 辅助:通知一次绘图调用完成
    void notifyDrawCall(const char* opName, int64_t durationUs) {
        // 标记画布需要重绘
        if (_client) _client->onCanvasInvalidated();
        // 通知绘图调用信息(用于性能监控、JS回调等)
        if (_client) _client->onDrawCallDispatched(opName, durationUs);
    }
    int64_t getMonotonicUs() {
        struct timespec ts;
        clock_gettime(CLOCK_MONOTONIC, &ts);
        return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
    }
};

3.3 实现具体的 ClientImpl(外溢依赖的正确归宿)

// widget_canvas_context_client_impl.h
// 这个类处于"解耦实现层":
// 它知道 DomEntity 和 Application,这是它的职责
// 它把 Canvas 的通知成 DOM 事件和 JS 回调
#pragma once
#include "widget_canvas_context_client.h"  // 实现的接口
#include "dom/dom_entity.h"                // 合理:这里持有 DOM 依赖
#include "app/application.h"               // 合理:这里持有 App 依赖
#include "jse/jse_context.h"               // 合理:这里持有 JS 引擎依赖
class WidgetCanvasContextClientImpl : public WidgetCanvasContextClient {
public:
    // 构造函数:在正确的层次持有外部依赖
    WidgetCanvasContextClientImpl(
        jse_context_ref  jsCtx,        // JS引擎:在这里持有,合理
        DomEntity*       canvasDom,    // DOM实体:在这里持有,合理
        Application*     app           // 应用层:在这里持有,合理
    )
        : _jsCtx(jsCtx)
        , _canvasDom(canvasDom)
        , _app(app)
    {}
    // ---- 实现 WidgetCanvasContextClient 接口 ----
    // Canvas 内容变化 → 触发 DOM 重绘
    void onCanvasInvalidated() override {
        if (_canvasDom) {
            // 告知 DOM 树该节点需要重新渲染
            _canvasDom->markDirty();
            _canvasDom->scheduleLayout();
        }
    }
    // 新帧就绪 → 传递给 JS 和 DOM
    void onFrameReady(const CanvasPixelBuffer& frame) override {
        // 1. 更新 DOM 的纹理数据
        if (_canvasDom) {
            _canvasDom->updateTexture(
                frame.data, frame.width, frame.height
            );
        }
        // 2. 触发 JS 的 onframe 回调(如果有注册)
        if (_jsCtx) {
            triggerJSCallback("__onCanvasFrame__", [&](jse_value_t* args) {
                // 将像素数据包装为 JS ArrayBuffer
                args[0] = jse_create_array_buffer(
                    _jsCtx,
                    frame.data,
                    frame.width * frame.height * 4
                );
                args[1] = jse_create_number(_jsCtx, frame.width);
                args[2] = jse_create_number(_jsCtx, frame.height);
                return 3;  // 参数个数
            });
        }
    }
    // Canvas 错误 → 通知 Application 和 JS 错误处理
    void onError(int code, const char* message) override {
        // 1. 通知 Application 层记录日志
        if (_app) {
            _app->onComponentError("WidgetCanvas", code, message);
        }
        // 2. 触发 JS 的 onerror 回调
        if (_jsCtx) {
            triggerJSCallback("__onCanvasError__", [&](jse_value_t* args) {
                jse_value_t errObj = jse_create_object(_jsCtx);
                jse_set_named_property(
                    _jsCtx, errObj, "code",
                    jse_create_number(_jsCtx, code)
                );
                jse_set_named_property(
                    _jsCtx, errObj, "message",
                    jse_create_string(_jsCtx, message)
                );
                args[0] = errObj;
                return 1;
            });
        }
        // 3. 触发 DOM 错误事件
        if (_canvasDom) {
            DomEvent errEvent("error");
            errEvent.detail = message;
            _canvasDom->dispatchEvent(errEvent);
        }
    }
    // 绘图调用完成 → 通知 JS 性能监控回调
    void onDrawCallDispatched(const char* opName,
                               int64_t     durationUs) override {
        if (!_jsCtx) return;
        // 触发 JS 的 ondraw 钩子(用于性能分析)
        triggerJSCallback("__onDrawCall__", [&](jse_value_t* args) {
            args[0] = jse_create_string(_jsCtx, opName);
            args[1] = jse_create_number(_jsCtx, (double)durationUs);
            return 2;
        });
    }
    // Canvas 尺寸变化 → 更新 DOM 布局
    void onSizeChanged(int newWidth, int newHeight) override {
        if (_canvasDom) {
            _canvasDom->setSize(newWidth, newHeight);
            _canvasDom->triggerReflow();  // 触发 DOM 重排
        }
        if (_jsCtx) {
            triggerJSCallback("__onCanvasResize__", [&](jse_value_t* args) {
                args[0] = jse_create_number(_jsCtx, newWidth);
                args[1] = jse_create_number(_jsCtx, newHeight);
                return 2;
            });
        }
    }
private:
    // 在正确的层次持有这些依赖——这里持有是完全合理的
    jse_context_ref  _jsCtx;      // JS 引擎上下文
    DomEntity*       _canvasDom;  // Canvas 对应的 DOM 节点
    Application*     _app;        // 应用层引用
    // 辅助:统一的 JS 回调触发逻辑
    using ArgBuilder = std::function<int(jse_value_t*)>;
    void triggerJSCallback(const char* callbackName, ArgBuilder buildArgs) {
        // 在全局对象上查找回调函数
        jse_value_t global = jse_get_global(_jsCtx);
        jse_value_t fn     = jse_get_named_property(
            _jsCtx, global, callbackName
        );
        jse_free_value(_jsCtx, global);
        if (!jse_value_is_function(_jsCtx, fn)) {
            jse_free_value(_jsCtx, fn);
            return;  // 没有注册回调,直接返回
        }
        // 构造参数
        jse_value_t args[8];
        int argCount = buildArgs(args);
        // 调用 JS 函数
        jse_value_t result = jse_call_function(
            _jsCtx, fn,
            jse_make_undefined(_jsCtx),
            args, argCount
        );
        // 释放所有 JS 值(RAII 此处用手动释放,避免 JS 引擎的特殊要求)
        jse_free_value(_jsCtx, fn);
        jse_free_value(_jsCtx, result);
        for (int i = 0; i < argCount; ++i) {
            jse_free_value(_jsCtx, args[i]);
        }
    }
};

3.4 顶层组装:Application 负责连接一切

// application.cpp 或 widget_factory.cpp
// 只有这里知道所有具体类型,负责把各层连接起来
class CanvasWidgetFactory {
public:
    // 创建一个完整可用的 Canvas Widget
    // 参数:JS引擎上下文 + 所属的 DOM 节点
    static WidgetCanvasContextModule* create(
        jse_context_ref jsCtx,
        DomEntity*      domNode,
        Application*    app
    ) {
        // 1. 创建核心绘图上下文(Canvas 最底层)
        auto* context = new WidgetCanvasContext2D(
            domNode->getWidth(),
            domNode->getHeight()
        );
        // 2. 创建具体的 ClientImpl(解耦实现层)
        // 外溢依赖(jsCtx、domNode、app)在这里被正确地封装
        auto* clientImpl = new WidgetCanvasContextClientImpl(
            jsCtx,    // JS引擎:在 ClientImpl 层持有
            domNode,  // DOM节点:在 ClientImpl 层持有
            app       // Application:在 ClientImpl 层持有
        );
        // 3. 创建 Canvas 模块(只传入核心 + 接口)
        // WidgetCanvasContextModule 完全不知道 jsCtx、domNode、app 的存在!
        auto* canvasModule = new WidgetCanvasContextModule(
            context,    // 核心绘图对象
            clientImpl  // 只是一个 WidgetCanvasContextClient*,不知道是 Impl
        );
        return canvasModule;
        // 生命周期管理:实际项目中用 unique_ptr 或自定义 RAII
    }
};
// 在 Application 初始化时使用:
void Application::initUI() {
    jse_context_ref jsCtx = _jsEngine->getContext();
    DomEntity* canvasDom  = _domTree->getElementById("main-canvas");
    // 一行创建,依赖关系在工厂内部正确组装
    _canvas = CanvasWidgetFactory::create(jsCtx, canvasDom, this);
    // 使用:完全干净的 API,看不到任何 JS 或 DOM 的痕迹
    _canvas->setFillColor(0xFF0000FF);
    _canvas->fillRect(0, 0, 100, 100);
    _canvas->commit();
}

四、分层设计的核心收益

4.1 可测试性对比

// ✗ 重构前:测试 Canvas 绘图,需要初始化整个前端运行时
void testBeforeRefactor() {
    JSEngine     jsEngine;               // 启动 JS 引擎(耗时 200ms)
    jse_context_ref ctx = jsEngine.createContext();
    DOMTree      domTree;                // 构建 DOM 树
    DomEntity*   entity = domTree.createElement("canvas");
    Application  app;                   // 初始化整个应用
    WidgetCanvasContext2D context;
    // 终于能创建被测对象了...
    WidgetCanvasContextModule module(ctx, &context, entity);
    module.fillRect(0, 0, 10, 10);      // 测这一行
    // 总耗时:几百毫秒,依赖真实硬件/环境
}
// ✓ 重构后:测试 Canvas 绘图,只需要 Mock
class MockCanvasClient : public WidgetCanvasContextClient {
public:
    int invalidateCount = 0;
    std::vector<std::string> drawCalls;
    void onCanvasInvalidated() override    { ++invalidateCount; }
    void onFrameReady(const CanvasPixelBuffer&) override {}
    void onError(int, const char*) override {}
    void onDrawCallDispatched(const char* op, int64_t) override {
        drawCalls.emplace_back(op);
    }
    void onSizeChanged(int, int) override {}
};
void testAfterRefactor() {
    WidgetCanvasContext2D context(100, 100);  // 离屏,无硬件依赖
    MockCanvasClient      mockClient;         // 零依赖
    WidgetCanvasContextModule module(&context, &mockClient);
    module.fillRect(0, 0, 10, 10);  // 直接测,无需任何前端运行时
    assert(mockClient.drawCalls[0] == "fillRect");
    assert(mockClient.invalidateCount == 1);
    // 总耗时:<1ms,纯内存操作,可在任何 CI 环境运行
}

4.2 可扩展性:新增通知目标零成本

// 分层设计的开放封闭原则(OCP)体现:
// 需要新增一个"性能监控通知目标"?
// ✓ 只需新增一个 ClientImpl,不修改任何现有代码
class PerformanceMonitorClient : public WidgetCanvasContextClient {
public:
    explicit PerformanceMonitorClient(PerformanceDB* db) : _db(db) {}
    void onCanvasInvalidated() override {}
    void onFrameReady(const CanvasPixelBuffer& f) override {
        // 记录帧率数据
        _db->recordFrame(f.width * f.height, getCurrentMs());
    }
    void onError(int code, const char*) override {
        _db->recordError(code);
    }
    void onDrawCallDispatched(const char* op, int64_t us) override {
        // 记录每个绘图调用的耗时
        _db->recordDrawCall(op, us);
    }
    void onSizeChanged(int, int) override {}
private:
    PerformanceDB* _db;
};
// 同时通知多个 Client(组合模式)
class CompositeCanvasClient : public WidgetCanvasContextClient {
public:
    void add(WidgetCanvasContextClient* c) { _clients.push_back(c); }
    void onCanvasInvalidated() override {
        for (auto* c : _clients) c->onCanvasInvalidated();
    }
    void onFrameReady(const CanvasPixelBuffer& f) override {
        for (auto* c : _clients) c->onFrameReady(f);
    }
    void onError(int code, const char* msg) override {
        for (auto* c : _clients) c->onError(code, msg);
    }
    void onDrawCallDispatched(const char* op, int64_t us) override {
        for (auto* c : _clients) c->onDrawCallDispatched(op, us);
    }
    void onSizeChanged(int w, int h) override {
        for (auto* c : _clients) c->onSizeChanged(w, h);
    }
private:
    std::vector<WidgetCanvasContextClient*> _clients;
};
// 使用组合:Canvas 模块无需任何修改
void Application::initUI() {
    auto* jsClient   = new WidgetCanvasContextClientImpl(ctx, dom, this);
    auto* perfClient = new PerformanceMonitorClient(&_perfDB);
    auto* composite  = new CompositeCanvasClient();
    composite->add(jsClient);    // 原有功能
    composite->add(perfClient);  // 新增性能监控
    // Canvas 模块接收的还是 WidgetCanvasContextClient*,毫不知情
    _canvas = new WidgetCanvasContextModule(&_context, composite);
}

五、分层原则总结

分层设计的本质是对信息流向的管控:
信息可以从上层流向下层(上层知道下层存在)\text{信息可以从上层流向下层(上层知道下层存在)}信息可以从上层流向下层(上层知道下层存在)
信息不能从下层流向上层(下层不知道上层存在)\text{信息不能从下层流向上层(下层不知道上层存在)}信息不能从下层流向上层(下层不知道上层存在)
下层需要"向上通知"时⇒用抽象接口反转依赖方向\text{下层需要"向上通知"时} \Rightarrow \text{用抽象接口反转依赖方向}下层需要"向上通知"用抽象接口反转依赖方向
这三条规则统一起来就是依赖倒置原则(DIP) 在分层架构中的具体应用:
WidgetCanvasContextModule⏟下层→WidgetCanvasContextClient(接口)⏟稳定抽象←WidgetCanvasContextClientImpl⏟上层\underbrace{\text{WidgetCanvasContextModule}}_{\text{下层}} \rightarrow \underbrace{\text{WidgetCanvasContextClient(接口)}}_{\text{稳定抽象}} \leftarrow \underbrace{\text{WidgetCanvasContextClientImpl}}_{\text{上层}}下层 WidgetCanvasContextModule稳定抽象 WidgetCanvasContextClient(接口)上层 WidgetCanvasContextClientImpl
下层定义接口,上层实现接口。依赖的箭头全部指向接口,没有任何跨层的直接指向,毛线团彻底解开

经验2:Delegate/Client 解决反向依赖

implements

implements

1 to 1

1 to N

弱引用

弱引用

弱引用

Server

Service

ServiceManager

ServiceRecoder

«interface»

Server::Delegate

«interface»

ServiceRecorder::Delegate

ServerDelegateImpl

ServiceRecorderDelegate

橙色框:ServiceManager 模块
包含 ServerDelegateImpl
和 ServiceRecorderDelegate

一、核心思想

“反向依赖"是指下层模块需要通知上层模块时,若直接持有上层的引用,就违反了分层原则。Delegate 模式是解决这个问题的经典手法:
反向依赖问题:下层→上层⇒违反分层原则\text{反向依赖问题} : \text{下层} \rightarrow \text{上层} \quad \Rightarrow \quad \text{违反分层原则}反向依赖问题:下层上层违反分层原则
Delegate 解法:下层→Delegate(接口)⏟下层自己定义←上层实现⇒依赖方向正确\text{Delegate 解法} : \text{下层} \rightarrow \underbrace{\text{Delegate(接口)}}_{\text{下层自己定义}} \leftarrow \text{上层实现} \quad \Rightarrow \quad \text{依赖方向正确}Delegate 解法:下层下层自己定义 Delegate(接口)上层实现依赖方向正确
关键洞察:接口由下层定义,上层来实现。下层只知道"有人在听我”,不知道是谁。

二、架构图中每个角色的职责

从 Mermaid 图出发,逐一理解每个类:

角色地图:
┌─────────────────────────────────────────────────────────────┐
│                    外部模块(图的两侧)                       │
│   Server(服务器)          Service(具体服务实例)           │
└───────────┬──────────────────────────┬──────────────────────┘
            │                          │
┌───────────▼──────────────────────────▼──────────────────────┐
│               橙色框:ServiceManager 模块                     │
│                                                               │
│   ServiceManager(核心调度器)                                │
│       │ 1:1                │ 1:N                             │
│       ▼                    ▼                                  │
│     Server            ServiceRecoder                         │
│                                                               │
│   ServerDelegateImpl       ServiceRecorderDelegate           │
│   (实现 Server::Delegate) (实现 ServiceRecorder::Delegate) │
└─────────────────────────────────────────────────────────────┘
            │                          │
┌───────────▼──────────────────────────▼──────────────────────┐
│                    接口层(最稳定)                            │
│   Server::Delegate          ServiceRecorder::Delegate        │
└─────────────────────────────────────────────────────────────┘

三、完整 C++ 实现

3.1 底层模块:Server 及其 Delegate 接口

// server.h
// Server 是底层模块,负责网络连接、数据收发
// 它需要向上层通知各种事件,但不能直接依赖上层
// 解法:定义内嵌的 Delegate 接口,上层来实现
#pragma once
#include <cstdint>
#include <cstddef>
class Server {
public:
    // ---- 内嵌 Delegate 接口(下层自己定义)----
    // Server 只知道"有一个监听者",不知道监听者是谁
    class Delegate {
    public:
        virtual ~Delegate() = default;
        // Server 连接建立
        virtual void onServerConnected(Server* server) = 0;
        // Server 收到数据
        virtual void onServerDataReceived(Server*        server,
                                           const uint8_t* data,
                                           size_t         len) = 0;
        // Server 连接断开
        virtual void onServerDisconnected(Server*     server,
                                           int         reason) = 0;
        // Server 发生错误
        virtual void onServerError(Server*     server,
                                    int         code,
                                    const char* msg) = 0;
    };
    // ---- Server 的核心接口 ----
    bool start(const char* host, uint16_t port);
    void stop();
    bool send(const uint8_t* data, size_t len);
    bool isConnected() const { return _connected; }
    // 注入 Delegate(由上层在构造/初始化时传入)
    void setDelegate(Delegate* delegate) {
        _delegate = delegate;
    }
protected:
    // 内部事件触发(网络层回调时调用这些方法)
    void notifyConnected() {
        if (_delegate) _delegate->onServerConnected(this);
    }
    void notifyDataReceived(const uint8_t* data, size_t len) {
        if (_delegate) _delegate->onServerDataReceived(this, data, len);
    }
    void notifyDisconnected(int reason) {
        if (_delegate) _delegate->onServerDisconnected(this, reason);
    }
    void notifyError(int code, const char* msg) {
        if (_delegate) _delegate->onServerError(this, code, msg);
    }
private:
    Delegate* _delegate  = nullptr;  // 持有接口,不持有具体类
    bool      _connected = false;
};

3.2 底层模块:ServiceRecoder 及其 Delegate 接口

// service_recoder.h
// ServiceRecoder 负责管理单个 Service 的生命周期和状态记录
// 同样需要向上层(ServiceManager)汇报状态变化
#pragma once
#include "service.h"
class ServiceRecoder {
public:
    // ---- 内嵌 Delegate 接口 ----
    class Delegate {
    public:
        virtual ~Delegate() = default;
        // Service 状态变化通知
        virtual void onServiceStarted(ServiceRecoder* recoder,
                                       Service*        service) = 0;
        virtual void onServiceStopped(ServiceRecoder* recoder,
                                       Service*        service,
                                       int             exitCode) = 0;
        virtual void onServiceCrashed(ServiceRecoder* recoder,
                                       Service*        service,
                                       const char*     reason) = 0;
        // 服务性能指标上报
        virtual void onServiceMetrics(ServiceRecoder* recoder,
                                       float           cpuUsage,
                                       size_t          memBytes) = 0;
    };
    // ---- ServiceRecoder 核心接口 ----
    explicit ServiceRecoder(Service* service)
        : _service(service) {}
    bool startService();
    bool stopService();
    bool restartService();
    Service*    getService()  const { return _service; }
    bool        isRunning()   const { return _running; }
    float       getCpuUsage() const { return _cpuUsage; }
    size_t      getMemBytes() const { return _memBytes; }
    void setDelegate(Delegate* delegate) {
        _delegate = delegate;
    }
    // Service 通过弱引用回调 ServiceRecoder(见 3.5 节)
    // 这里提供给 Service 调用的接口
    void onServiceHeartbeat(float cpu, size_t mem) {
        _cpuUsage = cpu;
        _memBytes = mem;
        // 向上汇报性能指标
        if (_delegate) _delegate->onServiceMetrics(this, cpu, mem);
    }
private:
    Service*  _service   = nullptr;
    Delegate* _delegate  = nullptr;  // 持有接口,不持有 ServiceManager
    bool      _running   = false;
    float     _cpuUsage  = 0.0f;
    size_t    _memBytes  = 0;
};

3.3 核心调度层:ServiceManager + 两个 Delegate 实现

// service_manager.h
// ServiceManager 是系统的核心调度模块(橙色框内容)
// 它管理 1个Server 和 N个ServiceRecoder
// 同时它实现了 Server::Delegate 和 ServiceRecoder::Delegate
// 通过这两个实现类接收来自下层的通知
#pragma once
#include "server.h"
#include "service_recoder.h"
#include <vector>
#include <memory>
// 前向声明
class ServiceManager;
// ---- ServerDelegateImpl:实现 Server::Delegate ----
// 职责:接收 Server 的网络事件,转发给 ServiceManager 处理
// 注意:持有 ServiceManager 的弱引用,防止循环引用
class ServerDelegateImpl : public Server::Delegate {
public:
    // 弱引用:不影响 ServiceManager 的生命周期
    // 使用原始指针 + 生命周期保证,或 wp<ServiceManager>
    explicit ServerDelegateImpl(ServiceManager* manager)
        : _manager(manager)  // 弱引用(不增加引用计数)
    {}
    void onServerConnected(Server* server) override;
    void onServerDataReceived(Server*        server,
                               const uint8_t* data,
                               size_t         len) override;
    void onServerDisconnected(Server* server, int reason) override;
    void onServerError(Server* server, int code, const char* msg) override;
private:
    ServiceManager* _manager;  // 弱引用!不是 sp<>,避免循环引用
    // 循环引用分析:
    // ServiceManager 持有 ServerDelegateImpl(强引用)
    // ServerDelegateImpl 持有 ServiceManager(若强引用 → 循环!)
    // 解法:ServerDelegateImpl 用弱引用持有 ServiceManager
};
// ---- ServiceRecorderDelegate:实现 ServiceRecoder::Delegate ----
// 职责:接收 ServiceRecoder 的服务状态事件,转发给 ServiceManager
class ServiceRecorderDelegate : public ServiceRecoder::Delegate {
public:
    explicit ServiceRecorderDelegate(ServiceManager* manager)
        : _manager(manager)  // 弱引用
    {}
    void onServiceStarted(ServiceRecoder* recoder,
                           Service*        service) override;
    void onServiceStopped(ServiceRecoder* recoder,
                           Service*        service,
                           int             exitCode) override;
    void onServiceCrashed(ServiceRecoder* recoder,
                           Service*        service,
                           const char*     reason) override;
    void onServiceMetrics(ServiceRecoder* recoder,
                           float           cpu,
                           size_t          mem) override;
private:
    ServiceManager* _manager;  // 弱引用!
};
// ---- ServiceManager:系统核心调度器 ----
class ServiceManager {
public:
    ServiceManager();
    ~ServiceManager();
    // 初始化:创建 Server 并注入 Delegate
    bool init(const char* host, uint16_t port);
    // 注册一个新的 Service
    bool registerService(Service* service);
    // 启动/停止所有服务
    bool startAll();
    void stopAll();
    // 查询
    size_t getServiceCount() const { return _recoders.size(); }
    bool   isServerConnected() const;
    // 供 Delegate Impl 调用的内部接口
    // (Delegate Impl 通过弱引用调用这些方法)
    void handleServerConnected();
    void handleServerData(const uint8_t* data, size_t len);
    void handleServerDisconnected(int reason);
    void handleServiceStarted(Service* service);
    void handleServiceStopped(Service* service, int exitCode);
    void handleServiceCrashed(Service* service, const char* reason);
    void handleServiceMetrics(ServiceRecoder* recoder,
                               float cpu, size_t mem);
private:
    // 1:1 管理一个 Server
    std::unique_ptr<Server>               _server;
    // 1:N 管理多个 ServiceRecoder
    std::vector<std::unique_ptr<ServiceRecoder>> _recoders;
    // 两个 Delegate 实现(橙色框内的组成部分)
    // ServiceManager 强持有这两个实现对象
    std::unique_ptr<ServerDelegateImpl>       _serverDelegate;
    std::unique_ptr<ServiceRecorderDelegate>  _serviceRecorderDelegate;
};

3.4 ServiceManager 的实现

// service_manager.cpp
#include "service_manager.h"
#include <cstdio>
// ---- ServerDelegateImpl 实现 ----
void ServerDelegateImpl::onServerConnected(Server* server) {
    // 弱引用使用前必须检查有效性
    if (_manager) {
        _manager->handleServerConnected();
    }
}
void ServerDelegateImpl::onServerDataReceived(Server*        server,
                                               const uint8_t* data,
                                               size_t         len) {
    if (_manager) {
        _manager->handleServerData(data, len);
    }
}
void ServerDelegateImpl::onServerDisconnected(Server* server,
                                               int     reason) {
    if (_manager) {
        _manager->handleServerDisconnected(reason);
    }
}
void ServerDelegateImpl::onServerError(Server*     server,
                                        int         code,
                                        const char* msg) {
    // 错误处理:记录日志,但不崩溃
    fprintf(stderr, "[ServerDelegate] error %d: %s\n", code, msg);
    if (_manager) {
        // 可以转发给 Manager 决定是否重连
        _manager->handleServerDisconnected(code);
    }
}
// ---- ServiceRecorderDelegate 实现 ----
void ServiceRecorderDelegate::onServiceStarted(ServiceRecoder* recoder,
                                                Service*        service) {
    if (_manager) {
        _manager->handleServiceStarted(service);
    }
}
void ServiceRecorderDelegate::onServiceStopped(ServiceRecoder* recoder,
                                                Service*        service,
                                                int             exitCode) {
    if (_manager) {
        _manager->handleServiceStopped(service, exitCode);
    }
}
void ServiceRecorderDelegate::onServiceCrashed(ServiceRecoder* recoder,
                                                Service*        service,
                                                const char*     reason) {
    fprintf(stderr, "[ServiceRecorder] crashed: %s\n", reason);
    if (_manager) {
        _manager->handleServiceCrashed(service, reason);
    }
}
void ServiceRecorderDelegate::onServiceMetrics(ServiceRecoder* recoder,
                                                float           cpu,
                                                size_t          mem) {
    if (_manager) {
        _manager->handleServiceMetrics(recoder, cpu, mem);
    }
}
// ---- ServiceManager 实现 ----
ServiceManager::ServiceManager() {
    // 创建两个 Delegate 实现,传入 this 作为弱引用
    // 注意:此时 ServiceManager 已经存在,弱引用安全
    _serverDelegate           = std::make_unique<ServerDelegateImpl>(this);
    _serviceRecorderDelegate  = std::make_unique<ServiceRecorderDelegate>(this);
}
ServiceManager::~ServiceManager() {
    stopAll();
    // _server、_recoders、_serverDelegate、_serviceRecorderDelegate
    // 均由 unique_ptr 自动释放,析构顺序:
    // 1. _serviceRecorderDelegate 析构(Impl 对象消失)
    // 2. _serverDelegate 析构
    // 3. _recoders 析构(ServiceRecoder 们消失)
    // 4. _server 析构(Server 消失)
    // 不会有悬空指针,因为 Impl 先析构,Server 后析构
}
bool ServiceManager::init(const char* host, uint16_t port) {
    _server = std::make_unique<Server>();
    // 把 ServerDelegateImpl 注入给 Server
    // Server 持有 Delegate 接口指针(不知道是 ServerDelegateImpl)
    _server->setDelegate(_serverDelegate.get());
    return _server->start(host, port);
}
bool ServiceManager::registerService(Service* service) {
    auto recoder = std::make_unique<ServiceRecoder>(service);
    // 把 ServiceRecorderDelegate 注入给每个 Recoder
    recoder->setDelegate(_serviceRecorderDelegate.get());
    _recoders.push_back(std::move(recoder));
    return true;
}
bool ServiceManager::startAll() {
    for (auto& recoder : _recoders) {
        if (!recoder->startService()) return false;
    }
    return true;
}
void ServiceManager::stopAll() {
    for (auto& recoder : _recoders) {
        recoder->stopService();
    }
}
// 处理来自 ServerDelegateImpl 的事件
void ServiceManager::handleServerConnected() {
    fprintf(stdout, "[Manager] Server connected, starting services...\n");
    startAll();  // 服务器连接后启动所有服务
}
void ServiceManager::handleServerData(const uint8_t* data, size_t len) {
    // 解析数据,路由到对应的 ServiceRecoder
    // 例如:根据 serviceId 找到对应的 recoder 处理
    uint32_t serviceId = parseServiceId(data, len);
    for (auto& recoder : _recoders) {
        if (recoder->getService()->getId() == serviceId) {
            recoder->getService()->handleData(data + 4, len - 4);
            return;
        }
    }
}
void ServiceManager::handleServerDisconnected(int reason) {
    fprintf(stdout, "[Manager] Server disconnected(%d), stopping...\n",
            reason);
    stopAll();
    // 可以触发重连逻辑
}
void ServiceManager::handleServiceStarted(Service* service) {
    fprintf(stdout, "[Manager] Service %u started\n", service->getId());
}
void ServiceManager::handleServiceStopped(Service* service, int exitCode) {
    fprintf(stdout, "[Manager] Service %u stopped, code=%d\n",
            service->getId(), exitCode);
}
void ServiceManager::handleServiceCrashed(Service*    service,
                                            const char* reason) {
    fprintf(stderr, "[Manager] Service %u crashed: %s, restarting...\n",
            service->getId(), reason);
    // 找到对应 recoder,触发重启
    for (auto& recoder : _recoders) {
        if (recoder->getService() == service) {
            recoder->restartService();
            return;
        }
    }
}
void ServiceManager::handleServiceMetrics(ServiceRecoder* recoder,
                                            float           cpu,
                                            size_t          mem) {
    // 聚合所有服务的资源使用情况
    if (cpu > 0.9f) {
        fprintf(stderr, "[Manager] Service CPU overload: %.1f%%\n",
                cpu * 100);
    }
}

3.5 弱引用处理循环引用

图中有两处"弱引用"标注,这是整个设计的关键安全机制:

// 循环引用分析:
// 潜在循环1:
// ServiceManager ──strong──→ ServerDelegateImpl
// ServerDelegateImpl ──strong──→ ServiceManager  ← 循环!
//
// 如果用强引用:两者互相持有,永远不会释放(内存泄漏)
//
// 解法:ServerDelegateImpl 用弱引用持有 ServiceManager
// ServiceManager ──strong──→ ServerDelegateImpl
// ServerDelegateImpl ──weak──→ ServiceManager    ← 打破循环
// 使用 Vela 的 wp<T>(弱指针)实现弱引用
class ServerDelegateImpl : public Server::Delegate {
public:
    // 用 wp<ServiceManager> 而非 ServiceManager*
    explicit ServerDelegateImpl(const sp<ServiceManager>& manager)
        : _manager(manager)  // wp<> 构造:不增加强引用计数
    {}
    void onServerConnected(Server* server) override {
        // 使用弱引用时,必须先 promote 为强引用
        // 如果 ServiceManager 已被销毁,promote 返回 nullptr
        sp<ServiceManager> manager = _manager.promote();
        if (manager == nullptr) {
            // ServiceManager 已析构,不再处理
            return;
        }
        // promote 成功:在此作用域内 manager 保证有效
        manager->handleServerConnected();
        // 离开作用域,manager(临时强引用)自动释放
    }
private:
    wp<ServiceManager> _manager;  // 弱引用:不影响生命周期
};
// 潜在循环2(图底部的弱引用):
// ServiceRecoder ──strong──→ Service(管理关系)
// Service ──weak──→ ServiceRecoder(回调心跳)
//
// Service 需要定期回调 ServiceRecoder 汇报心跳
// 若 Service 强持有 ServiceRecoder:
//   ServiceRecoder 强持有 Service(管理)
//   Service 强持有 ServiceRecoder(回调)← 循环!
//
// 解法:Service 用弱引用持有 ServiceRecoder
class Service {
public:
    // Service 弱引用持有所属的 ServiceRecoder
    void setRecoder(const sp<ServiceRecoder>& recoder) {
        _recoder = recoder;  // 存为弱引用
    }
    // 定期心跳(由内部定时器调用)
    void heartbeat() {
        sp<ServiceRecoder> recoder = _recoder.promote();
        if (recoder) {
            float cpu = measureCpuUsage();
            size_t mem = measureMemUsage();
            // 通过弱引用升级后安全回调
            recoder->onServiceHeartbeat(cpu, mem);
        }
        // 若 recoder 已析构,promote 失败,心跳静默丢弃
    }
private:
    wp<ServiceRecoder> _recoder;  // 弱引用:不阻止 ServiceRecoder 析构
    uint32_t           _id = 0;
};

弱引用的生命周期保证:
强引用计数>0⇒对象存活\text{强引用计数} > 0 \Rightarrow \text{对象存活}强引用计数>0对象存活
强引用计数=0⇒对象析构,但控制块保留直到弱引用计数也为0\text{强引用计数} = 0 \Rightarrow \text{对象析构,但控制块保留直到弱引用计数也为0}强引用计数=0对象析构,但控制块保留直到弱引用计数也为0
wp::promote()={sp<T>(有效)若强引用计数>0nullptr若强引用计数=0\text{wp::promote()} = \begin{cases} \text{sp<T>(有效)} & \text{若强引用计数} > 0 \\ \text{nullptr} & \text{若强引用计数} = 0 \end{cases}wp::promote()={sp<T>(有效)nullptr若强引用计数>0若强引用计数=0

3.6 整体组装与使用

// main.cpp 或 app_init.cpp
// 只有最顶层才知道所有具体类型,负责把所有模块连接起来
#include "service_manager.h"
#include "service.h"
int main() {
    // 1. 创建 ServiceManager(橙色框)
    // 构造时自动创建 ServerDelegateImpl 和 ServiceRecorderDelegate
    auto manager = std::make_shared<ServiceManager>();
    // 2. 创建具体的 Service 实例
    auto serviceA = std::make_shared<Service>(/*id=*/1, "AudioService");
    auto serviceB = std::make_shared<Service>(/*id=*/2, "NetworkService");
    auto serviceC = std::make_shared<Service>(/*id=*/3, "StorageService");
    // 3. 注册 Service(内部创建 ServiceRecoder,注入 Delegate)
    manager->registerService(serviceA.get());
    manager->registerService(serviceB.get());
    manager->registerService(serviceC.get());
    // 4. 建立 Service → ServiceRecoder 的弱引用(心跳回调)
    // (实际中在 registerService 内部完成)
    // 5. 初始化 Server(内部注入 ServerDelegateImpl)
    if (!manager->init("127.0.0.1", 8080)) {
        fprintf(stderr, "Server init failed\n");
        return -1;
    }
    // 6. 运行主循环
    // 此后:
    // Server 事件 → ServerDelegateImpl → ServiceManager
    // Service 心跳 → ServiceRecorderDelegate → ServiceManager
    // ServiceManager 统一调度,不被任何下层直接依赖
    runEventLoop();
    return 0;
}

四、Delegate 模式的本质总结

4.1 与直接回调的对比

// ✗ 不用 Delegate:下层直接持有上层(反向依赖)
class Server {
    ServiceManager* _manager;  // Server 知道 ServiceManager 的存在!
    void onConnected() {
        _manager->handleServerConnected();  // 下层调用上层,违规
    }
};
// ✓ 用 Delegate:下层只知道接口
class Server {
    Server::Delegate* _delegate;  // Server 只知道 Delegate 接口
    void onConnected() {
        if (_delegate) _delegate->onServerConnected(this);  // 通过接口
    }
    // Server 完全不知道 ServiceManager 的存在!
};

4.2 Delegate 模式的依赖方向

Server⏟下层→定义Server::Delegate⏟接口←实现ServerDelegateImpl⏟上层→弱引用ServiceManager⏟上层核心\underbrace{\text{Server}}_{\text{下层}} \xrightarrow{\text{定义}} \underbrace{\text{Server::Delegate}}_{\text{接口}} \xleftarrow{\text{实现}} \underbrace{\text{ServerDelegateImpl}}_{\text{上层}} \xrightarrow{\text{弱引用}} \underbrace{\text{ServiceManager}}_{\text{上层核心}}下层 Server定义 接口 Server::Delegate实现 上层 ServerDelegateImpl弱引用 上层核心 ServiceManager
所有箭头从上层指向下层,或指向接口。没有任何下层指向上层的箭头,分层原则完全满足。

4.3 三个设计决策的原因


决策 原因
Delegate 接口定义在下层(Server 内部) 接口描述的是下层"能报告什么",下层最清楚
Delegate 实现在上层(ServiceManager 模块内) 上层决定"如何处理"这些事件
Impl 用弱引用持有 ServiceManager 打破 Manager→Impl→Manager 的循环引用
Service 用弱引用持有 ServiceRecoder 打破 Recoder→Service→Recoder 的循环引用

这四个决策共同构成了一个无环、无泄漏、可测试的模块化设计。

经验3:避免因实现/需求定接口

一、核心问题:接口是郑重承诺

接口(Interface)不是实现细节的镜像,而是模块对外世界的契约
接口=模块对外的承诺≠模块内部实现的映射\text{接口} = \text{模块对外的承诺} \neq \text{模块内部实现的映射}接口=模块对外的承诺=模块内部实现的映射
好接口的标准:稳定性↑最小化↑必要性↑\text{好接口的标准:} \quad \text{稳定性} \uparrow \quad \text{最小化} \uparrow \quad \text{必要性} \uparrow好接口的标准:稳定性最小化必要性
坏接口的成因:因实现需要加→接口膨胀→所有实现类被迫实现\text{坏接口的成因:} \quad \text{因实现需要加} \rightarrow \text{接口膨胀} \rightarrow \text{所有实现类被迫实现}坏接口的成因:因实现需要加接口膨胀所有实现类被迫实现

二、从图片还原问题代码

// ✗ 问题代码还原
// 原始设计:IApplication 是 Application 的接口
// i_application.h
class IApplication {
public:
    virtual ~IApplication() = default;
    // ---- 原本合理的接口 ----
    virtual bool   init()    = 0;
    virtual void   start()   = 0;
    virtual void   stop()    = 0;
    virtual void   destroy() = 0;
    // ---- 问题接口:因 Application 实现需要加,所以接口也加了 ----
    // 第 317 行:
    virtual void notifyEventSync(int evt) = 0;
    // 注释里写着:
    // "因 Application 需要加,所以给 IApplication 也加了"
    // 这正是"因实现定接口"的典型症状!
};
// application.h
class Application : public IApplication {
public:
    bool   init()    override;
    void   start()   override;
    void   stop()    override;
    void   destroy() override;
    // Application 的某个功能需要 notifyEventSync
    // 于是直接在接口上加了这个方法
    void notifyEventSync(int evt) override {
        // 具体实现:同步通知某个事件
        for (auto& listener : _listeners) {
            listener->onEvent(evt);  // 同步分发
        }
    }
private:
    std::vector<EventListener*> _listeners;
};

三、为什么"只有一个实现"最容易出问题

通常情况:
IApplication ← Application(只有这一个实现!)
心理误区:
"反正就一个实现,接口和实现差不多是一回事"
"Application 需要这个方法,IApplication 加一下而已"
"以后也不会有第二个实现,无所谓"
实际后果(3个月后):
IApplication 变成了 Application 的"影子接口"
每次 Application 加功能,IApplication 跟着加
接口从 4 个方法膨胀到 40 个方法
新来的工程师:想实现一个 MockApplication 做测试
→ 被迫实现 40 个方法,其中 35 个完全不知道是干什么的

接口污染的增长模型:
∣It∣=∣I0∣+∑i=1tΔi|I_t| = |I_0| + \sum_{i=1}^{t} \Delta_iIt=I0+i=1tΔi
其中 Δi\Delta_iΔi 是第 iii 次迭代中"因实现需要"而添加的方法数。若每个月平均新增 Δ=3\Delta = 3Δ=3 个方法,111 年后:
∣I12∣=4+3×12=40(膨胀 10 倍)|I_{12}| = 4 + 3 \times 12 = 40 \quad \text{(膨胀 10 倍)}I12=4+3×12=40(膨胀 10 倍)
每个实现类的负担:
被迫实现的无关方法数=∣It∣−∣核心方法∣→∞\text{被迫实现的无关方法数} = |I_t| - |\text{核心方法}| \rightarrow \infty被迫实现的无关方法数=It核心方法

四、问题的三个层次

4.1 第一层:notifyEventSync 根本不该在接口里

// 追问:IApplication 的调用方为什么需要 notifyEventSync?
// 调用方A:ModuleManager(管理应用模块)
class ModuleManager {
    void initialize(IApplication* app) {
        app->init();    // 合理:ModuleManager 需要初始化 app
        app->start();   // 合理:ModuleManager 需要启动 app
        // 问题:ModuleManager 需要 notifyEventSync 吗?
        // app->notifyEventSync(EVT_MODULE_READY);  // 为什么需要这个?
    }
};
// 调用方B:TestHarness(测试框架)
class TestHarness {
    void runTest(IApplication* app) {
        app->init();
        app->start();
        // 测试框架需要 notifyEventSync 吗?
        // 99% 的情况:不需要!
        // 但因为接口有这个方法,测试框架被迫实现一个空的版本
    }
};
// 结论:notifyEventSync 是 Application 内部实现细节
// 没有任何外部调用方真正需要通过 IApplication 调用它
// 它被放在接口里,纯粹是因为"Application 加了这个方法"

4.2 第二层:接口职责不清晰

// ✗ 职责混乱的接口:把所有东西都塞进去
class IApplication {
public:
    // 生命周期(合理)
    virtual bool init()    = 0;
    virtual void start()   = 0;
    virtual void stop()    = 0;
    virtual void destroy() = 0;
    // 事件通知(不合理:这是内部实现细节)
    virtual void notifyEventSync(int evt)          = 0;
    virtual void notifyEventAsync(int evt)         = 0;
    virtual void registerEventListener(void* l)    = 0;
    virtual void unregisterEventListener(void* l)  = 0;
    // 配置(不合理:调用方不应该通过 IApplication 改配置)
    virtual void setDebugMode(bool on)             = 0;
    virtual void setLogLevel(int level)            = 0;
    virtual void setMaxMemory(size_t bytes)        = 0;
    // 状态查询(部分合理,部分不合理)
    virtual bool isRunning()    const = 0;  // 合理
    virtual int  getInternalThreadCount() const = 0;  // 不合理:内部细节
    virtual void* getRawContext() = 0;     // 不合理:暴露实现细节
};

五、解决方案

5.1 第一招:果断删除接口,只保留必要的核心约定

// ✓ 重新设计:IApplication 只包含调用方真正需要的方法
// 评估标准:如果没有这个方法,调用方还能正常工作吗?
//           能 → 删除;不能 → 保留
// i_application.h(精简后)
class IApplication {
public:
    virtual ~IApplication() = default;
    // ---- 只保留生命周期管理(这是外部调用方真正需要的)----
    // 每个方法都问:调用方为什么需要这个?
    virtual bool init()    = 0;  // 调用方需要初始化:✓ 保留
    virtual void start()   = 0;  // 调用方需要启动:✓ 保留
    virtual void stop()    = 0;  // 调用方需要停止:✓ 保留
    virtual void destroy() = 0;  // 调用方需要销毁:✓ 保留
    // 状态查询(调用方确实需要知道是否在运行)
    virtual bool isRunning() const = 0;  // ✓ 保留
    // ✗ 以下全部删除:
    // virtual void notifyEventSync(int evt) = 0;
    //   → 删除理由:没有调用方通过 IApplication 接口调用此方法
    //   → 这是 Application 内部的事件分发机制
    //
    // virtual void setDebugMode(bool on) = 0;
    //   → 删除理由:调试配置不是对外契约,是实现细节
    //
    // virtual void* getRawContext() = 0;
    //   → 删除理由:暴露内部数据结构,违反封装
};

5.2 第二招:将 notifyEventSync 放回 Application,不暴露给接口

// application.h(重构后)
// Application 实现 IApplication 的核心接口
// notifyEventSync 作为 Application 的私有/内部方法
#pragma once
#include "i_application.h"
#include <vector>
#include <functional>
class Application : public IApplication {
public:
    // ---- 实现 IApplication 接口(对外承诺)----
    bool init()    override;
    void start()   override;
    void stop()    override;
    void destroy() override;
    bool isRunning() const override { return _running; }
    // ---- Application 自己的扩展 API(不通过接口暴露)----
    // 这些方法只有知道具体类型 Application 的调用方才能用
    // 通过接口 IApplication* 的调用方看不到这些方法
    // 注册事件监听器(Application 层面的功能,不是接口层面的)
    void addEventListener(std::function<void(int)> listener) {
        _listeners.push_back(std::move(listener));
    }
private:
    // notifyEventSync 降级为私有方法!
    // 它只是 Application 内部的实现细节,外部不需要直接调用
    void notifyEventSync(int evt) {
        // 同步遍历所有监听器
        for (auto& listener : _listeners) {
            listener(evt);  // 同步分发
        }
    }
    void notifyEventAsync(int evt);  // 同理,也是私有
    bool _running = false;
    std::vector<std::function<void(int)>> _listeners;
};

5.3 第三招:接口添加评估流程

// 每次有人想给 IApplication 添加方法时,必须回答这四个问题:
/*
评估清单(添加接口前必须回答):
Q1. 哪些调用方(具体列举)需要通过 IApplication 调用这个方法?
    如果答案是"只有 Application 自己的内部代码"
    → 不应该加到接口,改为私有方法
Q2. 如果有第二个实现类(如 MockApplication),
    它能合理地实现这个方法吗?
    如果答案是"不知道怎么 Mock"或"Mock 没有意义"
    → 这个方法不属于接口
Q3. 这个方法在 6 个月后还会存在吗?
    如果它依赖于当前特定的实现细节
    → 不应该加到接口(接口应该稳定)
Q4. 不加这个接口,能否通过其他方式(事件、回调、组合)解决需求?
    如果答案是"能"
    → 用更好的方式解决,不要污染接口
*/
// 具体示例:notifyEventSync(int evt) 的评估
// Q1: 哪些调用方需要通过 IApplication 调用 notifyEventSync?
//     → 经过排查:没有任何外部模块通过 IApplication* 调用此方法
//     → 结论:不应该加到接口 ✗
// Q2: MockApplication 如何实现 notifyEventSync?
//     → Mock 版本不知道该做什么,只能写个空函数
//     → 说明这个方法对外部没有语义意义 ✗
// Q3: 6个月后还存在吗?
//     → notifyEventSync 依赖内部的 _listeners 容器实现细节
//     → 很可能随着实现变化而改变 ✗
// Q4: 能否不加接口方法解决?
//     → 完全可以:Application 内部私有调用即可 ✓
// 评估结论:4个问题3个不通过 → 坚决不加到 IApplication

5.4 第四招:从接口中拆分非核心逻辑

// ✓ 如果事件通知确实需要对外暴露,
// 应该拆分为独立的、职责单一的接口,而不是塞进 IApplication
// 方案:把事件相关功能提取为单独接口
class IEventEmitter {
public:
    virtual ~IEventEmitter() = default;
    virtual void addEventListener(std::function<void(int)> cb)    = 0;
    virtual void removeEventListener(std::function<void(int)> cb) = 0;
};
// IApplication 保持干净,只管生命周期
class IApplication {
public:
    virtual ~IApplication() = default;
    virtual bool init()          = 0;
    virtual void start()         = 0;
    virtual void stop()          = 0;
    virtual void destroy()       = 0;
    virtual bool isRunning() const = 0;
    // 绝对不加 notifyEventSync!
};
// Application 同时实现两个接口(各司其职)
class Application : public IApplication,
                    public IEventEmitter {
public:
    // IApplication 实现
    bool init()    override { /* ... */ return true; }
    void start()   override { _running = true; }
    void stop()    override { _running = false; }
    void destroy() override { /* ... */ }
    bool isRunning() const override { return _running; }
    // IEventEmitter 实现
    void addEventListener(std::function<void(int)> cb) override {
        _listeners.push_back(std::move(cb));
    }
    void removeEventListener(std::function<void(int)> cb) override {
        // 移除对应监听器(实际用 token/id 更好)
    }
private:
    // notifyEventSync 依然是私有方法
    void notifyEventSync(int evt) {
        for (auto& cb : _listeners) cb(evt);
    }
    bool _running = false;
    std::vector<std::function<void(int)>> _listeners;
};
// 使用:调用方根据需要选择接口
void moduleInit(IApplication* app) {
    app->init();   // 只用生命周期接口,看不到事件方法
}
void setupEvents(IEventEmitter* emitter) {
    emitter->addEventListener([](int evt) {
        printf("event: %d\n", evt);
    });
}
// App 组装时转换接口
Application app;
moduleInit(&app);               // 当作 IApplication 使用
setupEvents(&app);              // 当作 IEventEmitter 使用

六、接口设计的黄金法则

6.1 接口最小化原则(ISP:接口隔离原则)

// ✗ 胖接口:把所有东西塞在一起
class IApplication {
    virtual bool init()                          = 0;
    virtual void start()                         = 0;
    virtual void stop()                          = 0;
    virtual void notifyEventSync(int evt)        = 0;  // 事件
    virtual void setLogLevel(int level)          = 0;  // 配置
    virtual void dumpMemoryStats()               = 0;  // 调试
    virtual std::string getVersionString() const = 0;  // 版本
    // ...共30个方法
};
// ✓ 瘦接口:每个接口只做一件事
class IApplicationLifecycle {   // 生命周期(核心)
    virtual bool init()    = 0;
    virtual void start()   = 0;
    virtual void stop()    = 0;
    virtual void destroy() = 0;
};
class IEventEmitter {           // 事件(可选)
    virtual void on(int evt, std::function<void()> cb) = 0;
    virtual void off(int evt)                          = 0;
};
class IConfigurable {           // 配置(可选,仅特定调用方需要)
    virtual void configure(const Config& cfg) = 0;
};
// 调试相关:完全不应该在接口里,用条件编译或单独的调试模块

接口的方法数与维护代价的关系:
维护代价∝∣I∣2\text{维护代价} \propto |I|^2维护代价I2
因为每次修改接口,所有 nnn 个实现类都需要更新,而每个实现类的修改代价又与接口大小成正比:
总修改代价=n实现类×∣I∣×c单次修改\text{总修改代价} = n_{\text{实现类}} \times |I| \times c_{\text{单次修改}}总修改代价=n实现类×I×c单次修改
∣I∣|I|I303030 减小到 555,总修改代价降低 305=6\frac{30}{5} = 6530=6 倍。

6.2 接口稳定性检验

// 接口方法的稳定性自检:
// ✓ 稳定的接口方法特征:
//   - 描述"做什么",不描述"怎么做"
//   - 参数/返回值是稳定的数据类型(不是内部实现类型)
//   - 有清晰的语义,任何实现者都知道该做什么
virtual bool init() = 0;
// 稳定:init 的语义永远清晰,任何实现都知道怎么做
virtual void start() = 0;
// 稳定:start 语义明确,无歧义
// ✗ 不稳定的接口方法特征:
//   - 方法名包含实现细节(Sync/Async/Internal/Raw)
//   - 参数是内部数据结构
//   - 只有当前这一个实现知道该怎么实现
virtual void notifyEventSync(int evt) = 0;
// 不稳定:
//   "Sync"暗示了实现细节(同步 vs 异步)
//   第二个实现类应该实现同步还是异步?不清晰
//   如果改成异步,接口名字就错了
virtual void* getRawContext() = 0;
// 不稳定:
//   "Raw"暗示内部实现
//   不同实现的 context 完全不同,接口毫无意义
virtual void setInternalThreadCount(int n) = 0;
// 不稳定:
//   "Internal"已经说明这不该在接口里
//   线程数是实现细节,不是外部契约

6.3 Mock 测试作为接口合理性验证

// 最强的接口合理性验证手段:尝试写一个 MockApplication
// 如果某个方法难以 Mock,说明它不该在接口里
// ✓ 合理的接口:MockApplication 很容易实现
class MockApplication : public IApplication {
public:
    bool init() override {
        initCalled = true;
        return true;  // Mock 返回成功,简单直接
    }
    void start() override { startCalled = true; }
    void stop()  override { stopCalled  = true; }
    void destroy() override { destroyCalled = true; }
    bool isRunning() const override { return startCalled && !stopCalled; }
    // 测试断言用
    bool initCalled    = false;
    bool startCalled   = false;
    bool stopCalled    = false;
    bool destroyCalled = false;
};
// Mock 非常自然,每个方法都有明确的 Mock 实现 ← 说明接口设计合理
// ✗ 不合理的接口:notifyEventSync 如何 Mock?
class MockApplication : public IApplication {
    void notifyEventSync(int evt) override {
        // 做什么?
        // 空实现?那测试没有意义
        // 记录调用?但调用方根本不通过接口调用这个方法!
        // Mock 无意义 ← 强烈信号:这个方法不该在接口里
    }
};

七、总结:接口的郑重承诺

接口的本质是一种契约,一旦发布就要承担维护责任:
承诺的代价=∣接口方法数∣×∣实现类数∣×时间\text{承诺的代价} = |\text{接口方法数}| \times |\text{实现类数}| \times \text{时间}承诺的代价=接口方法数×实现类数×时间
越多的承诺⇒越大的维护负担⇒越难修改的系统\text{越多的承诺} \Rightarrow \text{越大的维护负担} \Rightarrow \text{越难修改的系统}越多的承诺越大的维护负担越难修改的系统
正确的接口设计哲学:
宁可少承诺,不可乱承诺\boxed{\text{宁可少承诺,不可乱承诺}}宁可少承诺,不可乱承诺
具体操作:

操作 何时执行
果断删除接口方法 发现某个方法只有一个实现且无外部调用方时
降级为私有方法 方法只服务于本类内部逻辑时
拆分为独立接口 方法属于不同的关注点时
通过事件/回调替代 需要通知外界但不想暴露细节时
做 Mock 验证合理性 每次添加新接口方法时

接口是模块对外的郑重承诺——每一个虚函数都是对所有调用方、所有实现者的永久约定。加之前三思,删时果断,这是接口设计的核心修养。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐