【问题标题】:How to implement classes derived from an abstract with different methods?如何用不同的方法实现从抽象派生的类?
【发布时间】:2017-09-03 08:04:19
【问题描述】:

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

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

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

显然main末尾的X-&gt;GetDiag()方法不能使用,因为它不是“poligon”类的方法。 具有这种逻辑的程序的正确实现是什么?

【问题讨论】:

  • 那么不要为运行时多态性而烦恼。根据用户输入调用不同的函数。

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


【解决方案1】:

在基类中引入方法

virtual bool boHasDiagonal(void) =0;

在基类中无条件声明:

virtual double GetDiag();

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

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

改变输出线:

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

对于偏执狂(我认为程序员的健康心态),请使用 Gluttton 的默认实现 GetDiag() 的概念,这表示错误(如他在此处的回答中所示)。

对于许多poligons的情况,我喜欢评论中Rakete1111的提议。

【讨论】:

  • 因为只有某些类会覆盖boHasDiagonal 以返回true,如何在polygon 中为boHasDiagonal 提供默认定义以返回false?如果您有很多没有任何对角线的多边形,那将节省大量输入。
  • @Rakete1111 很好的接触。我想 GetDiag() 可以在语义上定义为“最长的对角线”,并且可以为所有 poligon 的一半实现,但你仍然是对的。实际上,它可能是“最长的点对点”并为所有人实施,但我猜这会很无聊并且违背 OP 的意图。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    您可以使用dynamic_cast

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

    请注意,您已经有一个错误:如果input == 1,您只会创建triangle,但如果input == 2,您会得到对角线。另外,上面的方法也不是很安全,因为如果转换无效,dynamic_cast 可以返回nullptr

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

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

    【讨论】:

      【解决方案4】:

      使用dynamic casting检查基类的指针是否真的是一个三角形,像这样:

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

      PS:我假设您的示例只是草稿,因为它有一些错误。

      【讨论】:

        【解决方案5】:

        您使用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-class 和另一个 diagonal-class 继承。 diagonal-class 只定义了GetDiag 函数,与polygon-class 没有真正的关系:

        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
        

        【讨论】:

          【解决方案6】:

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

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

          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。圈子呢?它们没有高度和长度。你能在你期望多边形的任何地方使用矩形吗?目前可以,但多边形可以有更多边,可以自相交等。我不能向矩形添加新边,否则它将不再是矩形。

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

          【讨论】:

            【解决方案7】:

            低调通常是糟糕设计的标志,在实践中很少需要。

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

            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.

            【讨论】:

              猜你喜欢
              • 2020-10-18
              • 2012-03-14
              • 2014-11-16
              • 1970-01-01
              • 1970-01-01
              • 2021-11-24
              • 1970-01-01
              • 1970-01-01
              • 2017-09-26
              相关资源
              最近更新 更多