Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does downcasting defeat the purpose of polymorphism?

I encountered a question today, found here, which raised this question for me.

Here's a pseudo-code example of what I'm getting at:

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();
        };
    };
};

In the example, there is a RichGuy class. Richguy only keeps track of his Cars in a single vector. Because he has so many Cars it would be too troublesome to keep track of them based on if they're a FordFocus or a Lamborghini. However, the only type of car he has with a retractable roof is the Lambo. In order to retractTheRoof(), RichGuy must now determine if the Car he has is indeed a Lamboghini, and then downcast to execute this function of it.

Based on this example, was the choice to downcast in good design? Or did it violate the purpose of polymorphism, assuming that purpose is to allow derived classes to define their own behavior, and provide a common interface for classes like RichGuy? And if so, is there a better way to allow for functions like retractTheRoof() (or at least it's effect) to be available to RichGuy to use?

like image 952
Anne Quinn Avatar asked Aug 26 '11 05:08

Anne Quinn


People also ask

What is the need of downcasting in Java?

Why we need Upcasting and Downcasting? In Java, we rarely use Upcasting. We use it when we need to develop a code that deals with only the parent class. Downcasting is used when we need to develop a code that accesses behaviors of the child class.

What is downcasting in oops?

In class-based programming, downcasting or type refinement is the act of casting a reference of a base class to one of its derived classes.

What is the use of Upcasting and downcasting in C#?

What is upcasting and downcasting? Upcasting is conversion from a derived (child) class to a base (parent) class. Going up the family tree. Downcasting is the opposite going from a base class to a derived class, down the tree.

Why the concept of down casting is not allowed?

Downcasting is not allowed without an explicit type cast. The reason for this restriction is that the is-a relationship is not, in most of the cases, symmetric. A derived class could add new data members, and the class member functions that used these data members wouldn't apply to the base class.


1 Answers

Now if there are more than one type of cars which is retractable, say such cars are CarA, CarB, and CarC (in addition to Lamborghini), then are you going to write this:

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();
}

So a better design in such cases would be this: add an interface called IRetractable and derive from it as well:

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 {
   //...
}; 

Then you can simply write this:

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

Cool? Isn't it?

Online demo : http://www.ideone.com/1vVId

Of course, this still uses dynamic_cast, but the important point here is that you're playing with interfaces only, no need to mention concrete class anywhere. In other words, the design still makes use of runtime-polymorphism as much as possible. This is one of the principle of Design Patterns:

"Program to an 'interface', not an 'implementation'." (Gang of Four 1995:18)

Also, see this:

  • What does it mean to "program to an interface"?

Other important point is that you must make the destructor of Car (base class) virtual:

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

Its imporant because you're maintaining a vector of Car*, that means, later on you would like to delete the instances through the base class pointer, for which you need to make ~Car() a virtual destructor, otherwise delete car[i] would invoke undefined behaviour.

like image 169
Nawaz Avatar answered Sep 26 '22 02:09

Nawaz