Структура кода для разных версий устройств - PullRequest
2 голосов
/ 20 мая 2019

Я пишу «драйвер устройства» (C ++ 14), который может обрабатывать несколько версий протоколов, предназначенных для разных версий устройств.Этот драйвер устройства работает на внешнем ПК, который связывается с устройством через Ethernet по протоколу HTTP.Есть общие функции для всех версий, но некоторые функции могут быть дополнительными в определенных версиях протокола.

Ниже приведен пример:

class ProtocolBase {
public:
    virtual void reset_parameters() {
        std::cout << "reset parameters" << std::endl;
    }

    virtual void set_parameters() {
        std::cout << "set parameters" << std::endl;
    }
};

class ProtocolV1 : public ProtocolBase
{
public:
    void set_parameters() override {
        std::cout << "set parameters for V1" << std::endl;
    }
};

class ProtocolV2 : public ProtocolBase 
{
public:
    void set_parameters() override {
        std::cout << "set parameters for V2" << std::endl;
    }

    void reset_parameters() {
        std::cout << "reset parameters for V2" << std::endl;
    }

    void do_V2() {
        std::cout << "doing V2" << std::endl;
    }
};

Ниже main:

int main(int argc, char const *argv[])
{
    int version = std::atoi(argv[1]);

    std::unique_ptr<ProtocolBase> protocol = std::make_unique<ProtocolV1>();
    switch (version)
    {
    case 1:
        /* do nothing at the moment */
        break;
    case 2:
        protocol.reset(new ProtocolV2);
        break;
    default:
        break;
    }

    protocol->reset_parameters();

    if(ProtocolV2* p = dynamic_cast<ProtocolV2*>(protocol.get())) { //not sure about this
        p->do_V2();
    }else {
        std::cout << "This functionality is unavailable for this device" << std::endl;
    }
    protocol->set_parameters();
    return 0;
}

У меня такое чувство, что использование dynamic_cast - не лучший способ попасть сюда.Ждем некоторых отзывов.

Редактировать: Согласно ответу @ Ptaq666, я изменил ProtocolBase и ProtocolV2 как:

class ProtocolBase {
public:
    virtual void do_V(){
        std::cerr << "This functionality is unavailable for this device" << std::endl;
    }
};
class ProtocolV2 : public ProtocolBase 
{
public:
    void do_V() override {
        std::cout << "doing V2" << std::endl;
    }
};

С этим, естьбольше не нужно dynamic_cast, хотя базовый класс должен знать все функции.На данный момент это лучшее решение.

Ответы [ 2 ]

0 голосов
/ 20 мая 2019

Как и в большинстве случаев, когда дело доходит до выбора подходящей архитектуры системы, ответ «это зависит» :).Наиболее удобным решением было бы ввести специфичное для протокола поведение подклассов ProtocolBase в их конструкторах

class ProtocolV2 : public ProtocolBase 
{
public:
    ProtocolV2::ProtocolV2(args) {
        // set some params that will determine when do_V2() is called
        // it can be some numeric setting, a callback, or similar
    }
    void set_parameters() override {
        // you can use V2 behavior here maybe?
        std::cout << "set parameters for V2" << std::endl;
    }

    void reset_parameters() override {
        // or here maybe?
        std::cout << "reset parameters for V2" << std::endl;
    }

private:
    void do_V2() {
        std::cout << "doing V2" << std::endl;
    }
};

Если по какой-либо причине вы не можете сделать это, есть возможность сохранить do_V2() как общедоступныйне виртуальный метод, но вызывать его перед передачей ProtocolV2 в качестве указателя на ProtocolBase системной системе, которая будет его использовать.Конечно, ограничение заключается в том, что do_V2 можно вызывать только за пределами области вашей системы, что не может реально решить проблему.

Другой вариант - фактически переместить do_V2() в интерфейс:

class ProtocolBase {
public:
    virtual void reset_parameters() {
        std::cout << "reset parameters" << std::endl;
    }
    virtual void set_parameters() {
        std::cout << "set parameters" << std::endl;
    }
    virtual void do_V2() {
        std::cout << "not supported" << std::endl;
    }
};

и реализовать его как «не поддерживаемое» поведение по умолчанию.Только ProtocolV2 будет реализовывать это поведение в качестве допустимой части протокола.

В конце концов, если ничего из вышеперечисленного не подходит, вы, конечно, можете использовать dynamic_cast, как вы предложили.Лично я стараюсь избегать dynamic_cast, потому что мои коллеги по офису наверняка начнут злоупотреблять им, но в некоторых случаях это правильное решение.

Также, если вы решите навести указатель, используйте std::shared_ptr сdynamic_pointer_cast вместо доступа к необработанному указателю из unique_ptr.

0 голосов
/ 20 мая 2019

В общем случае это зависит от того, как формируются производные классы ProtocolV1 и ProtocolV2, и каковы члены данных и погода, если соответствующие функции-члены будут использовать разные члены данных или нет!

Причина в том, что нет никакой зависимости от данных-членов, функции-члены чувствительны только к типу объектов, с которыми они были вызваны, а не к их значению / состоянию !

Это похоже на перегрузку функции (не являющейся членом), например:

void function(ProtocolV1 *){
        std::cout << "set parameters for V1" << std::endl;
}
void function(ProtocolV2 *){
        std::cout << "set parameters for V2" << std::endl;
}

И затем вызов ее один раз указателем типа ProtocolV1 * и один раз с нулевым указателем типа ProtocolV2 *.

Если вам нравятся альтернативы для использования, которое вы представили в вопросе, вы даже можете использовать приведение в стиле C: в результате получилось ЖЕ !

Наконец, если вы собираетесь вызывать функцию-член для последующего вызова другой функции из нее, для которой требуется некоторый элемент (ы) данных, который отличается для производных классов в качестве аргумента / ов,n Вы не можете использовать любое приведение, если не введете какую-либо форму компенсации для заполнения данных, которые не представлены в приведенном типе!

Удачи!

...