Как использовать композицию вместо наследования при внедрении зависимости? - PullRequest
0 голосов
/ 01 октября 2018

У меня в программе есть куча шашек, которые я смоделировал как классы: проверь, хорошо ли ОЗУ, в порядке ли диск, проверь температуру и т. Д. У этих шашек много общего, поэтому я их смоделировалс наследованием: все общее объединяется в базовый класс 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();
}

РЕДАКТИРОВАТЬ: Обновлено, чтобы включить приблизительное значение, как используются шашки.A MasterChecker периодически запускает все отдельные контролеры.Он имеет список контролеров и вызывает их runTest() функцию-член, которую все контролеры переопределяют из своего базового класса.

1 Ответ

0 голосов
/ 01 октября 2018

... композиция должна быть предпочтительнее наследования

Это означает, , где вы можете выбрать любой, предпочитайте композицию .В этом случае MasterChecker (правильно) составляет различные конкретные контролеры, как рекомендовано вашим советом.

Тот факт, что отдельные контролеры наследуют / реализуют абстрактный базовый класс, не является проблемой, потому что вы можете 't составляет интерфейс.Здесь нет выбора, и в совете не говорилось, что вы никогда не должны использовать наследование, даже если композиция не является альтернативой.

Случай, против которого на самом деле предупреждает ваш совет, заключается в следующем:

class MasterChecker: public DiskChecker, public TemperatureChecker

, где наследование используется для агрегирования подобъектов базового класса.

В вашем случае это, вероятно, не сработает в любом случае, по крайней мере без изменений, из-за порядка инициализации и причин наследования в форме ромба.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...