如何使用不同的方法实现从抽象派生的类?

时间:2017-09-03 08:04:19

标签: c++ class oop methods abstract-class

我已经实现了从抽象类派生的不同类,每个类都有不同的方法。问题是我必须只在运行时声明对象,所以我必须创建一个指向基类的指针,我不能使用每个派生类的方法。

我创建了一个例子来更好地解释我的意思:

#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 !!!
    }
}

显然,不能使用主要末尾的方法X->GetDiag(),因为它不是&#34; poligon&#34;的方法。类。 使用这种逻辑的程序的正确实现是什么?

7 个答案:

答案 0 :(得分:3)

在基类中引入一个方法

//float groupSpace = 0.06f;
float groupSpace = 0.01 f;
float barSpace = 0.02 f; // x2 dataset
//float barWidth = 0.45f; // x2 dataset
float barWidth = 0.1 f; // x2 dataset
// (0.02 + 0.45) * 2 + 0.06 = 1.00 -> interval per "group"
data.setBarWidth(barWidth); // set the width of each bar
// mChart.setData(data);
mChart.groupBars(0 f, groupSpace, barSpace); // perform the "explicit" 
grouping
mChart.invalidate(); // refresh
// mChart.getBarData().setBarWidth(barWidth);
//mChart.getXAxis().setAxisMinValue(startYear);
mChart.getXAxis().setAxisMaximum(i - 1);
//mChart.groupBars(0, groupSpace, barSpace);
mChart.setFitBars(true);

mChart.invalidate();
// mChart.groupBars(0, groupSpace, barSpace);

在基类中无条件声明:

virtual bool boHasDiagonal(void) =0;

在两个派生类中以不同方式实现它:

virtual double GetDiag();

更改输出行:

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

对于一种偏执狂(在我看来是程序员的健康心态),使用Gluttton的概念if (X->boHasDiagonal()) {cout << "Diangonal = " << X->GetDiag() << endl;} 的默认实现,它表示错误(如他的答案)。

对于很多poligons的情况,我喜欢Rakete1111在评论中提出的建议。

答案 1 :(得分:1)

在基类中定义定义实现的方法抛出异常:

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

在具有有意义实现的类中覆盖它:

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

用法:

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

答案 2 :(得分:0)

您可以使用dynamic_cast

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

请注意,您已经有一个错误:只有triangle才能创建input == 1,但如果input == 2,则会获得对角线。此外,上述内容并不十分安全,因为如果转换无效,dynamic_cast可以返回nullptr

但最好检查dynamic_cast是否成功,然后您也可以放弃input == 2支票:

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

答案 3 :(得分:0)

您使用dynamic_cast来访问子类方法。 如果它不是从类派生的,则返回nullptr。这就是所谓的向下投射,因为你要沿着班级树走下去:

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

编辑:您可以将第一行中的声明放入if条件中,该条件超出了if语句之外的范围:

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

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

如果您想允许,multiple subclasses具有GetDiag功能,您可以继承poligon - 类和另一个diagonal - 类。 diagonal - 类仅定义GetDiag函数,并且与polygon - 类没有关系:

class polygon {
    // stays the same
};

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

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

如上所述,您可以通过使用dynamic_cast进行投射来访问这些方法,但这次转换为diagonal类型。这次是侧面投射,因为poligondiagonal无关,所以你在树中横向移动。

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

答案 4 :(得分:0)

使用dynamic casting检查基类&#39;指针实际上是一个三角形,如下所示:

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

PS:我认为你的例子只是草稿,因为它有一些错误。

答案 5 :(得分:0)

正如其他人所说,您可以使用dynamic_cast更改程序中的静态类型,使用伪实现向基类添加方法或使用某种形式的类型切换。但是,我会将所有这些答案视为程序设计缺陷的标志,并拒绝代码。它们都将关于程序中存在的类型的假设编码到代码中并造成维护负担。想象一下,为程序添加新类型的形状。然后,您必须搜索并修改dynamic_cast对象的所有位置。

我认为您的示例层次结构首先是错误的。当你为ploygons声明一个基类并从中派生三角形时,多态的整个目的是能够以相同的方式处理类似的对象。所以任何不常见的行为( not implementation )都放在基类中。

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

你明确地说我可以用程序中的三角形实例替换任何多边形实例。这是Liskov substitution principle。圈子怎么样?他们没有高度和长度。您可以在任何期望多边形的地方使用矩形吗?目前你可以,但多边形可以有更多的边,自相交等。我不能为一个矩形添加一个新的边,否则它将是一个矩形。

有一些解决方案,但由于这是一个设计问题,解决方案取决于您想要对象做什么。

答案 6 :(得分:0)

垂头丧气通常是设计糟糕的表现,在实践中很少需要。

我不明白为什么在这种特殊情况下需要它。您无缘无故地丢弃了有关哪种类型的信息。另一种选择可能是:

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