涉及依赖注入时如何使用组合而不是继承?

Posted

技术标签:

【中文标题】涉及依赖注入时如何使用组合而不是继承?【英文标题】:How to use composition instead of inheritance when dependency injection is involved? 【发布时间】:2018-10-01 09:38:10 【问题描述】:

我的程序中有一堆检查器,我将它们建模为类:检查 RAM 是否正常,检查磁盘是否正常,检查温度是否正常等。这些检查器有很多共同点,所以我对它们进行了建模继承:所有共同点都进入基类CheckerBase,该基类派生自具有检查器特定功能和依赖关系的专用类。

但是我经常读到组合应该优先于继承,所以我想知道在 C++ 中如何使用组合来完成?

#include <chrono>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;

/** Dependencies of various checkers that I pass in via dependency injection. */
struct ErrorReporter 
    void report_error(string myMsg) 
        cout << myMsg;
    
;
struct TemperatureSensor 
    int get_cpu_temp()  return 42; 
    int get_disk_temp()  return 32; 
;
struct DiskStressor 
    void stress_disk()  
;

/** Contains dependencies that are common to all checkers.. */
class CheckerBase 
public:
    CheckerBase(ErrorReporter* errReporter ) :
        mErrReporter(errReporter)  

    virtual void runTest() = 0;
protected:
    ErrorReporter* mErrReporter;
;

/** Needs `TemperatureSensor` dependency. */
class TemperatureChecker : public CheckerBase 
public:
    TemperatureChecker(ErrorReporter* errReporter,
                       TemperatureSensor* tempSensor) :
        CheckerBase(errReporter), mTempSensor(tempSensor)  

    void runTest() override 
        if (mTempSensor->get_cpu_temp() > 42) 
            mErrReporter->report_error("CPU too hot");
        
     ;
private:
    TemperatureSensor* mTempSensor;
;

/** Needs `TemperatureSensor` and `DiskStressor` dependencies. */
class DiskChecker : public CheckerBase 
public:
    DiskChecker(ErrorReporter* errReporter, TemperatureSensor* tempSensor,
                DiskStressor* diskStressor) :
        CheckerBase(errReporter), mTempSensor(tempSensor)  

    void runTest() override 
        mDiskStressor->stress_disk();
        mTempSensor->get_disk_temp();
        if (mTempSensor->get_cpu_temp() > 32) 
            mErrReporter->report_error("HDD too hot after strees test");
        
     ;
private:
    TemperatureSensor* mTempSensor;
    DiskStressor* mDiskStressor;
;

/** Periodically runs each checker. */
class MasterChecker 
    public:
        MasterChecker() :
            mTempChecker  &mErrReporter, &mTempSensor ,
            mDiskChecker  &mErrReporter, &mTempSensor, &mDiskStressor ,
            mAllCheckers(&mTempChecker, &mDiskChecker) ;

        void start() 
            // In reality I use a timer that continously runs each checker at
            // a certain interval.
            while (true) 
                for (CheckerBase *checker : mAllCheckers) 
                    checker->runTest();
                
                this_thread::sleep_for(chrono::milliseconds(5000));
            
        
    private:
        ErrorReporter mErrReporter;
        TemperatureSensor mTempSensor;
        DiskStressor mDiskStressor;

        DiskChecker mDiskChecker;
        TemperatureChecker mTempChecker;

        vector<CheckerBase*> mAllCheckers;
;

int main() 
    MasterChecker master;
    master.start();

编辑:更新以包含检查器使用方式的近似值。 MasterChecker 定期运行所有单独的检查器。它有一个检查器列表并调用它们的runTest() 成员函数——所有检查器都从它们的基类覆盖。

【问题讨论】:

这个 sn-p 对我来说没有多大意义:你如何从你的跳棋中提取信息? isOK() 方法在哪里? @YSC 这只是继承如何建模的一个示例。假设依赖项具有执行检查器类使用的功能的功能。或者我应该以某种方式改进我的 sn-p? 您打算使用类的方式对于提出基于组合的设计至关重要。 您的用法或继承在这里是正确的。你不能有抽象成员,所以无论如何你不能有成员CheckerBase 来作文。 在尝试组合事物时应该首选组合。您已经在MasterChecker 中执行此操作,因为您组合/聚合了各种检查器,而不是从它们派生。实现抽象接口不是组合,继承就可以了。 【参考方案1】:

...组合应该优先于继承

这意味着,你可以选择其中一个,更喜欢组合。在这种情况下,MasterChecker(正确)按照您的建议组成了各种具体的检查器。

各个检查器继承/实现抽象基类这一事实不是问题,因为您不能组合接口。这里别无选择,建议并没有说即使组合不是替代方案,也不应该使用继承。

您的建议实际上警告的情况是:

class MasterChecker: public DiskChecker, public TemperatureChecker

滥用继承来聚合基类子对象。

在您的情况下,由于初始化顺序和菱形继承的原因,至少在没有更改的情况下,这可能不会很好地工作。

【讨论】:

以上是关于涉及依赖注入时如何使用组合而不是继承?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用带有角度依赖注入的打字稿继承

如何对继承对象进行单元测试?

使用Composition时如何避免子类回调?

优先使用对象组合,而不是类继承

优先使用对象组合,而不是类继承

优先使用对象组合,而不是类继承