简体   繁体   中英

How to implement classes derived from an abstract with different methods?

I have implemented different classes derived from an abstract class and each one has different methods. The problem is that I have to declare the object only at runtime, so I have to create a pointer to the base class and I can't use the methods of each derived class.

I have created an example to explain better what I mean:

#include <iostream>

using namespace std;

class poligon
{
public:
    double h, l;
    void setPoligon(double h, double l) {
        this->h = h;
        this->l = l;
    }
    virtual double GetArea() = 0;
    virtual void GetType() = 0;
};


class triangle : public poligon
{
    double GetArea() { return l*h / 2; }
    void GetType() { cout << "triangle" << endl; }
    double GetDiag() { return sqrt(l*l + h*h); }
};


class rectangle : public poligon
{
    double GetArea() { return l*h; }
    void GetType() { cout << "rectangle" << endl; }
};


void main()
{
    poligon* X;
    int input;

    cout << "1 for triangle and 2 for rectangle: ";
    cin >> input;

    if (input == 1)
    {
        X = new triangle;
    }
    else if (input == 2)
    {
        X = new rectangle;
    }
    else
    {
        cout << "Error";
    }

    X->h = 5;
    X->l = 6;

    X->GetType();
    cout << "Area = " << X->GetArea() << endl;

    if (input == 2)
    {
        cout << "Diangonal = " << X->GetDiag() << endl;    // NOT POSSIBLE BECAUSE " GetDiag()" IS NOT A METHOD OF "poligon" CLASS !!!
    }
}

Obviously the method X->GetDiag() at the end of the main can't be used because it is not a method of the "poligon" class. Which is the correct implementation of a program with this logic?

Introduce a method in the base class

virtual bool boHasDiagonal(void) =0;

Declare unconditionally in base class:

virtual double GetDiag();

Implement it differently in both derived classes:

virtual bool boHasDiagonal(void) {return true;} // rectangle
virtual bool boHasDiagonal(void) {return false;} // triangle

Change output line:

if (X->boHasDiagonal())
{cout << "Diangonal = " << X->GetDiag() << endl;}

For a nice touch of paranoia (a healthy state of mind for a programmer in my opinion), use concept by Gluttton of a default implementation of GetDiag() , which signals an error (as in his answer here) .

For the case of many poligons, I like the proposal by Rakete1111 in the comment.

Define method in the base class which define implementation throws exception:

class poligon
{
public:
    virtual double GetDiag()
    {
        throw std::logic_error ("Called function with inappropriate default implementation.");
    }
};

In class that has meaningful implementation override it:

class rectangle : public poligon
{
    double GetDiag() override
    {
        return diagonale;
    }
};

Usage:

int main () {
    try {
        X->GetDiag();
    }
    catch (...) {
        std::cout << "Looks like polygon doesn't have diagonal." << std::endl;
    }
}

You can use dynamic_cast .

dynamic_cast<triangle*>(X)->GetDiag();

Note that you already have a bug: You only create a triangle if input == 1 , but you get the diagonal if input == 2 . Also, the above is not really safe, because dynamic_cast can return nullptr if the conversion is invalid.

But it would be better to check whether dynamic_cast succeeds, then you could also drop the input == 2 check:

if (triangle* tri = dynamic_cast<triangle*>(X))
    std::cout << "Diagonal = " << tri->GetDiag() << '\n';

You use dynamic_cast to access subclass-methods. It returns nullptr if it is not derived from the class. This is called down cast , as you are going down the class-tree:

triangle* R = dynamic_cast<triangle*>(X);
if(R) {
    cout << "Diagonale = " << R->GetDiag() << '\n';
};

Edit: You can put the declaration in the first line into the if-condition, which goes out of scope outside the if-statement:

if(triangle* R = dynamic_cast<triangle*>(X)) {
    cout << "Diagonale = " << R->GetDiag() << '\n';
};

if(rectangle* R = ...) {...}; // reuse of identifier

If you want to allow, that multiple subclasses have the GetDiag function you can inherit from the poligon -class and another diagonal -class. The diagonal -class only defines the GetDiag function and has not really to do with the polygon -class:

class polygon {
    // stays the same
};

class diagonal {
    virtual double GetDiag() = 0;
};

class triangle : public polygon, public diagonal {
    // body stays the same
};

And like above, you access the methods via casting with dynamic_cast but this time you cast to type diagonal . This time it is side cast , because poligon has nothing to do with diagonal , so you are going sideways in the tree.

   polygon         diagonal
    |   |             |
    |   |_____________|
    |          |
    |          |
rectangle   triangle

Use dynamic casting to check if the base class' pointer is actually a triangle, like this:

int main()
{
    ...
    if(triangle* t = dynamic_cast<triangle*>(X))
        std::cout << "Triangle's diagonal = " << t->GetDiag() << std::endl;
    return 0;
}

PS: I assume that your example is just a draft, since it has some bugs.

As others have said, you can use dynamic_cast to change the static type in your program, add a method to the base-class with a pseudo implementation or use some form of type-switching. However, I would consider all these answers as signs of a design flaw in your program and would reject the code. They all encode assumptions about the types existing in your program into the code and pose a maintenance burden. Imagine adding new types of shapes to your program. You then have to search and modify all the places you dynamic_cast your objects.

I think your example hierarchy is wrong in the first place. When you declare a base-class for ploygons, and derive triangles from it, the whole purpose of polymorphism is to be able to treat similar objects identically. So anything that is not common behavior ( not implementation ) is put in the base-class.

class poligon
{
public:
    double h, l;
    void setPoligon(double h, double l) {
        this->h = h;
        this->l = l;
    }
    virtual double GetArea() = 0;
    virtual void GetType() = 0;
};


class triangle : public poligon
{
    double GetArea() { return l*h / 2; }
    void GetType() { cout << "triangle" << endl; }
    double GetDiag() { return sqrt(l*l + h*h); }
};

You explicitly say that I can replace any instance of polygon with an instance of triangle everywhere in your program. This is the the Liskov substitution principle . What about circles? They don't have height and length. Can you use a rectangle everywhere you expect a polygon? Currently you can, but polygons can have more edges, be self-intersecting etc. I cannot add a new edge to a rectangle, otherwise it would be a rectangle anymore.

There are some solutions, but as it is a design question, the solution depends on what you want to do with the objects.

A downcast is usually a sign of a bad design and is rarely needed in practice.

I can't see why it is needed in this particular case. You have discarded the information about which type you have for no reason. An alternative could be:

void printDiagonal(const triangle& tri)
{
    std::cout << "Diangonal = " << tri.GetDiag() << std::endl;
}

void process(poligon& p)
{
    p.h = 5;
    p.l = 6;

    p.GetType();
    std::cout << "Area = " << p.GetArea() << std::endl;
}

int main()
{
    int input;

    std::cout << "1 for triangle and 2 for rectangle: ";
    std::cin >> input;

    if (input == 1)
    {
        triangle tri;
        process(tri);
        printDiagonal(tri);
    }
    else if (input == 2)
    {
        rectangle rect;
        process(rect);
    }
    else
    {
        std::cout << "Error\n";
    }
}

Live demo .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM