2013-02-09 4 views
1

Предположим, что у меня есть это базовый класс:Две иерархии связанных классов - наиважнейшие виртуальные функции

struct Vehicle { 
    //"op" stands for the operator of the vehicle 
    //examples: pilot, truck driver, etc. 
    virtual void insert_op(Op op) = 0; 
    //other members... 
}; 

И эти два подкласса

struct Truck : public Vehicle { 
    void insert_op(Op op) override { 
    //prepare Truck with truck driver 
    } 
}; 

struct Airplaine : public Vehicle { 
    void insert_op(Op op) override { 
    //prepare Airplaine with pilot 
    } 
}; 

Как вы догадываетесь, это другая иерархия:

struct Op {}; 
struct TruckDriver : public Op {}; 
struct Pilot : public Op {}; 

Вы уже видите эту проблему, не так ли? Я хочу, чтобы FORCE Truck согласился только с TruckDrivers и FORCE airplaines принять только пилотов, но это невозможно в текущем проекте. C++ не разрешает параметры diff для переопределенных виртуальных машин.

Я предполагаю, что я мог бы выполнить проверку типа времени типа «Op» в каждой из подклассов реализаций insert_op, но это звучит как действительно уродливое решение, а также не применяется во время компиляции.

Какие-нибудь пути?

ответ

3

Ваш Vehicle говорит virtual void insert_op(Op op), что означает «каждое транспортное средство может принимать любые Op».

Таким образом, в соответствии с вашим дизайном, Truck не является действительным кандидатом на Vehicle подкласса, потому что он не может принять любойOp - он может принимать только TruckDriver с.

Похожие: Liskov substitution principle


Проблема заключается в дизайне, а не в реализации этого. Я предлагаю упростить иерархию классов. Вам действительно нужно так много классов и наследования? Можете ли вы просто пойти с Vehicle и Op, у которых есть поля, идентифицирующие их тип?


Позвольте мне дальше объяснить проблему дизайна:

Пусть некоторый объект A с помощью метода manVehicle(Vehicle&). Truck - это подкласс транспортного средства, поэтому этот метод можно назвать объектом типа Truck.

Однако реализация A не имеет понятия о конкретных типах Vehicle. Он знает только, что у всех транспортных средств есть способ insert_op(Op), поэтому для его совершения можно позвонить, например, insert_op(Pilot()), даже если на самом деле это автомобиль Truck.

Выводы:

  • проверка времени компиляции даже не представляется возможным

  • проверка выполнения может работать ...

    • но лишь подметать проблему под ковер. Пользователь Vehicle s ожидает, что сможет позвонить по номеру insert_op(Op) на любой номер Vehicle.

Решение было бы изменить интерфейс Vehicle выглядеть следующим образом:

struct Vehicle { 
    virtual bool can_insert_op(Op op) = 0; 
    virtual void insert_op(Op op) = 0; 
}; 

и документировать его так, чтобы вызывающий абонент будет знать, что insert_op можно назвать только с Op с, удовлетворяющих can_insert_op на данных Vehicle. Или что-то подобное (например, задокументированное исключение из insert_op «недопустимый тип op для этого автомобиля») - все работает до тех пор, пока это документированная часть этого интерфейса.


BTW техническое замечание: Вы, вероятно, хотите, чтобы эти методы, чтобы взять Op по указателю или ссылке, а не копировать его, чтобы избежать ненужной копии, а также slicing.

+0

Скажете ли вы, что такие проверки времени выполнения распространены в хорошо разработанных программах на C++? – user2015453

+0

Я не думаю, что видел иерархию классов, для которой нужны такие проверки. – Kos

0

Подклассы транспортных средств должны каждый создавать свой соответствующий им Оператор. Не должно быть методов для установки оператора. У транспортного средства может быть метод get_operator, но это все.

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

0

не нашли против OOD. если водитель грузовика является ребенком водителя, то его водитель, но имеет другие функции. в вашем случае водитель не разрешается, а не водитель не грузовика. Если вы хотите придерживаться текущего дизайна, вам нужно сделать проверки в начале fn, как вы предполагали. Полиморфизм разработан, чтобы не получать ошибки @ время компиляции, следовательно, это динамическое связывание во время выполнения, а не скомпилировать время.

0

насчет:

struct Vehicle { 
    //"op" stands for the operator of the vehicle 
    //examples: pilot, truck driver, etc. 
    virtual void insert_op(Op op) = 0; 
    virtual bool validate_op(Op op); 
    //other members... 
}; 

struct Truck : public Vehicle { 
    void insert_op(Op op) override { 
    if (validate_op(op)) { 
     //prepare Truck with truck driver 
    } 
    } 
    bool validate_op(Op op) override { 
    //check if this is a valid truck driver 
    return (typeid(op)==typeid(TruckDriver)); 
    } 
}; 

Вы могли бы сохранить родовое определение insert_op с некоторой проверки над ним.

+0

Остерегайтесь, поскольку для сравнения 'typeid()' требуется определенный подтип 'Op',' TruckDriver', а не какой-либо подтип TruckDriver, для этого 'return dynamic_cast (op)' – EdMaster

0

То, что вы хотите выполнить, является законным, однако нет поддержки для хорошего решения. Это не проблема C++ сама, но Ориентация объекта:

Иерархия классов, начиная с Vehicle является ковариантны относительно иерархии Op. У вас была бы такая же проблема, если бы у вас была иерархия топлива. Или национальность Ops и т. Д.

Другие объяснили вам, как это сделать с проверками времени выполнения, но, конечно, всегда есть что-то, что нужно.

Если вы хотите выполнить полную проверку времени компиляции и тип полиморфизма, который вы хотите использовать, это время компиляции, а не время выполнения, вы можете использовать Generic Programming, templates.

Смежные вопросы