Преодолевает ли удручение цель полиморфизма? - PullRequest
17 голосов
/ 26 августа 2011

Я столкнулся с вопросом сегодня, нашел здесь , который поднял этот вопрос для меня.

Вот пример псевдокода того, что я получаю:

class Car{
public:
    virtual int goFast() = 0;
};


class FordFocus : public Car {
public:
    int goFast(){
        return 35;
    };
};


class Lamborghini : public Car {
    bool roof;
public:
    int goFast(){
        return -1/0;  // crash 
    };
    void retractTheRoof(){
        roof = 0;
    };
};



class RichGuy {
    vector<Car *> cars;
public:
    void goDrive() {

        for(int i = 0; i < cars.size(); ++i) {
            if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
                lambo->retractTheRoof();
            };
            goFast();
        };
    };
};

В этом примере есть класс RichGuy. Richguy только отслеживает его Cars в одном векторе. Поскольку у него так много Cars, было бы слишком сложно отслеживать их, основываясь на том, являются ли они FordFocus или Lamborghini. Тем не менее, единственный тип автомобиля с выдвижной крышей - это Lambo. Чтобы retractTheRoof(), RichGuy должен теперь определить, является ли Car, который он имеет, действительно Lamboghini, а затем понизиться, чтобы выполнить эту функцию этого.

Исходя из этого примера, был ли выбор уныл в хорошем дизайне? Или это нарушало цель полиморфизма, предполагая, что цель состоит в том, чтобы позволить производным классам определять свое собственное поведение и предоставлять общий интерфейс для таких классов, как RichGuy? И если да, то есть ли лучший способ сделать так, чтобы такие функции, как retractTheRoof() (или, по крайней мере, его эффект), были доступны для RichGuy для использования?

Ответы [ 4 ]

14 голосов
/ 26 августа 2011

Теперь, если есть более одного типа автомобилей, которые могут выдвигаться, скажем, такие автомобили CarA, CarB и CarC (в дополнение к Lamborghini), то вы собираетесь написать это:

if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
    lambo->retractTheRoof();
}
else if(CarA * pCarA = dynamic_cast<CarA*>(cars[i])) {
    pCarA->retractTheRoof();
}
else if(CarB * pCarB = dynamic_cast<CarB*>(cars[i])) {
    pCarB->retractTheRoof();
}
else if(CarC * pCarC = dynamic_cast<CarC*>(cars[i])) {
    pCarC->retractTheRoof();
}

Таким образом, лучший дизайн в таких случаях был бы следующим: добавьте интерфейс с именем IRetractable и извлекайте из него также:

struct IRetractable 
{
   virtual void retractTheRoof() = 0;
};

class Lamborghini : public Car, public IRetractable {
   //...
};

class CarA : public Car, public IRetractable {
   //...
};
class CarB : public Car, public IRetractable { 
   //...
};
class CarC : public Car, public IRetractable {
   //...
}; 

Тогда вы можете просто написать это:

if(IRetractable *retractable =  dynamic_cast<IRetractable *>(cars[i])) 
{
    retractable->retractTheRoof(); //Call polymorphically!
}

Cool? Не правда ли?

Онлайн демо: http://www.ideone.com/1vVId

Конечно, здесь все еще используется dynamic_cast, но важный момент в том, что вы играете только с интерфейсами , не нужно упоминать конкретный класс где угодно. Другими словами, в дизайне все еще максимально используется полиморфизм времени выполнения . Это один из принципов Design Patterns :

"Запрограммируйте на" интерфейс ", а не" реализацию "." (Банда четырех 1995: 18)

Также смотрите это:


Другим важным моментом является то, что вы должны сделать деструктор Car (базовый класс) виртуальным:

class Car{
public:
    virtual ~Car() {} //important : virtual destructor
    virtual int goFast() = 0;
};

Это важно, потому что вы поддерживаете вектор Car*, это означает, что позже вы захотите удалить экземпляры через указатель базового класса, для чего вам нужно сделать ~Car() виртуальным деструктором, в противном случае delete car[i] вызовет неопределенное поведение.

7 голосов
/ 26 августа 2011

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

Однако ваше перечисление MODEL совершенно бесполезно - вот что dynamic_cast на самом деле для .

if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
    lambo->retractTheRoof();
}
4 голосов
/ 26 августа 2011

Если вы заботитесь о убираемости крыши, вы можете иметь ConvertibleCar абстрактный базовый класс между Car и Lamborghini в цепочке наследования:

class Car {
  public :
    virtual int goFast() = 0;
};

class ConvertibleCar : public virtual Car {
  public :
    virtual void retractTheRoof() = 0;
};

class FordFocus : public Car {
  public :
    int goFast() { return 35; };
};

class Lamborghini : public ConvertibleCar {
    bool roof;
  public :
    int goFast() { return -1/0; /* crash */ };
    void retractTheRoof() { roof = 0; };
};

Если класс RichGuy не может отслеживать все различные типы автомобилей в отдельности, вы все равно можете использовать dynamic_cast, чтобы определить, относится ли определенный автомобиль к определенному типу:

ConvertibleCar* convertible = dynamic_cast<ConvertibleCar*>(cars[i]);
if (convertible) {
    convertible->retractTheRoof();
};

Обратите внимание, что это очень хорошо масштабируется для разных типов автомобилей (ConvertibleCar, AllTerrainCar, SportsCar, ...), где один и тот же автомобиль может наследовать от 0 или более из этих типов. Lamborghini вероятно будет производным от ConvertibleCar и SportsCar:

class Lamborghini : public SportsCar, public ConvertibleCar {
    // ...
};
3 голосов
/ 26 августа 2011

Вместо проверки «Lamborghini» в этом модуле, потому что я точно знаю, что Lamborghini - не единственный производитель автомобилей, производящий автомобили с убирающимися крышами.

Тогда вам не нужно динамическое литьесовсем.Вот как это должно было быть сделано.

class Car{
public:
    virtual int goFast() = 0;
    virtual void retractTheRoof(){ /*no-op*/}; // default
};

class Lamborghini : public Car {
    bool roof;
public:
    int goFast(){
        return -1/0;  // crash 
    };
    void retractTheRoof(){ roof = 0;}; // specific
};

и затем в коде вместо

if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
    lambo->retractTheRoof();
}

do

cars[i]->retractTheRoof();

Вот и все.

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