【问题标题】:What is the need Polymorphism in OOP? [closed]OOP 中需要多态性是什么? [关闭]
【发布时间】:2016-09-08 12:18:56
【问题描述】:

我有一个关于 OOP 概念的愚蠢问题,我们选择多态的原因是什么??

C++ 中的简单代码:

class Shape{
public:
virtual void draw(){ cout<<"Shape"<<endl;};
};

class Traingle: public Shape
{
public: void draw(){cout<<"Triangle"<<endl;}
};

class Rectangle: public Shape
{
public: void draw (){cout<<"Rectangle"<<endl;}
};

int main(){
Shape *ptr= new Traingle();
ptr->draw();
delete ptr;
return 7;
}

这里的ptr->draw()函数会调用Triangle draw,如果指向Rectangle则调用Rectangle draw,即后期绑定。

创建基类指针并将其指向不同的类有什么必要性?我们可以在没有任何虚函数的情况下创建单独的类对象,并根据需要调用。 喜欢

int main(){
    Traingle tObj; 
    tObj->draw();
    Rectangle rObj; 
    rObj->draw();
}

基本上做同样的事情;

为什么基本上是多态?为什么是虚拟的?

它有什么需要或者使用这个属性有什么不同? 真实案例会有所帮助!

【问题讨论】:

  • 在你的情况下不需要新的指针。但这与“需要什么(对于)多态性......”无关。
  • 虚函数的意义在于我们可以在运行时定义行为并允许动态使用对象。多态是给它的名字。
  • 例如,您可能想要创建形状集合。考虑std::vector&lt;Shape*&gt;。它可能包含不同的形状,但你可以对它们做共同的事情。
  • 或者创建一个single外部函数,void drawMe(Shape&amp; shape)。现在调用它两次,传递一个TriangleRectangle 实例。好了。
  • Polymorphism in c++的可能重复

标签: c++ oop polymorphism virtual


【解决方案1】:

神奇的词是解耦。假设您正在创建一个应用程序来管理超市的存储。您几乎从一开始就知道您迟早需要一种存储和检索数据的方法。但是,您还不知道哪种技术最好使用(例如,它可能是关系数据库或 NoSql)。

所以,你创建一个接口(抽象类):

struct Serialize{
    virtual void save(Product product) = 0;
    ... other method here ...
};

现在,您可以继续开发应用程序的其他部分,只需实现 Serialize 的内存版本。

class InMemorySerialize : public Serialize {
    ... implement stuff here ...
};

依赖于 Serialize 的代码并不关心使用的是哪个具体类,唯一真正需要更改以修改序列化实现的地方是具体类的构造。但是类的构造很可能只在一个地方并且非常接近主函数(查看strategy pattern 了解更多信息)。

//in the main (switching these lines you will use different implementations)
//the rest of your code will not change
unique_ptr<Serialize> serializer(new InMemorySerialize());
//unique_ptr<Serialize> serializer(new OnFileSerialize(myfolder));

此外,实现接口是实现Open/Close principle 的最简单方法之一。如果您想支持序列化到文件而不是内存中,您将创建一个新类,该类将位于单独的文件中。因此,您可以在不触及现有代码的情况下增加应用程序的功能。

最后,如果您依赖接口而不是具体对象,则测试会容易得多。您可以轻松实现测试所需的行为。

【讨论】:

    【解决方案2】:

    多态允许相关类型的对象被相同地对待,从而允许代码重用。

    考虑到您可能需要几十个行为不同的子类:

    struct Shape1: public Shape { /* .. */ }; // triangle
    struct Shape2: public Shape { /* .. */ }; // rectangle
    // ...
    struct ShapeN: public Shape { /* .. */ }; // projection of rhombic triacontahedron
    

    考虑您可能需要处理由Shape 指针数组指向的对象。

    使用多态性,您需要一个向量和一个带有虚函数调用的循环:

    std::vector<Shape*> v = get_shape_vector();
    for(Shape* s : v)
        s->draw();
    

    如果没有多态性,您将为每种类型管理一个单独的数组并分别处理它们:

    std::vector<Shape1> v1 = get_shape1_vector();
    std::vector<Shape2> v2 = get_shape2_vector();
    // ...
    std::vector<ShapeN> vN = get_shapeN_vector();
    
    for(Shape1& s : v1)
        s.draw();
    for(Shape2& s : v2)
        s.draw();
    // ...
    for(ShapeN& s : vN)
        s.draw();
    

    使用多态的 3 行代码比不使用多态的 3*N 行代码更容易维护。

    请考虑您可能需要修改流程。也许您想在绘图之前添加一个函数调用。当您拥有多态性时,这很简单:

    void pre_draw(Shape*);
    
    for(Shape* s : v) {
        pre_draw(s);
        s->draw();
    }
    

    没有多态性,你需要定义几十个函数并修改十几个循环中的每一个:

    void pre_draw1(Shape1&);
    void pre_draw2(Shape2&);
    // ...
    void pre_drawN(ShapeN&);
    
    for(Shape1& s : v1) {
        pre_draw1(s);
        s.draw();
    }
    for(Shape2& s : v1) {
        pre_draw2(s);
        s.draw();
    }
    // ...
    for(ShapeN& s : v1) {
        pre_drawN(s);
        s.draw();
    }
    

    考虑以后可以添加形状。使用多态,您只需要定义新类型和虚函数。您可以简单地将指向它的指针添加到数组中,它们将像其他所有兼容类型的对象一样被处理。

    struct ShapeN1: public Shape { /* .. */ }; // yet another shape
    

    如果没有多态性,除了定义新类型之外,您还必须为其创建一个新数组。您需要创建一个新的pre_draw 函数。而且您需要添加一个新循环来处理它们。

    void pre_drawN1(ShapeN1&);
    // ...
    std::vector<ShapeN1> vN1 = get_shapeN1_vector();
    // ...
    for(ShapeN1& s : vN1) {
        pre_drawN1(s);
        s.draw();
    }
    

    事实上,您需要遍历整个代码库以查找处理每种形状类型的位置,并在那里添加新类型的代码。


    现在,N 可能很小也可能很大。 N越大,避免的重复多态性越多。但是,无论您拥有多少子类,在添加新子类时不必查看整个代码库是一个很大的好处。

    【讨论】:

      【解决方案3】:

      简单地说,多态性允许你将不同的形状放在同一个盒子里,并以异构的方式对待盒子里的所有东西。

      扩展你的例子:

      #include <iostream>
      #include <memory>
      #include <vector>
      
      class Shape{
      public:
          virtual void draw(){ std::cout<<"Shape"<< std::endl;};
      };
      
      class Traingle: public Shape
      {
      public:
          void draw() override
          {
              std::cout<<"Triangle"<< std::endl;
          }
      };
      
      class Rectangle: public Shape
      {
      public:
          void draw() override
          {
              std::cout<<"Rectangle"<< std::endl;
          }
      };
      
      int main(){
      
          std::vector<std::unique_ptr<Shape>> box_of_shapes;
      
          box_of_shapes.emplace_back(new Traingle);
          box_of_shapes.emplace_back(new Rectangle);
      
          for (const auto& pshape : box_of_shapes)
          {
              pshape->draw();
          }
      
          return 0;
      }
      

      Rectangle 是一个 Shape(因为它是从Shape公开派生的)因此它可以由处理(指向)Shape的向量持有。 但是,因为draw 方法是虚拟的,所以即使调用者只是将Shape 视为RectangleRectangle 也会正确绘制。

      预期输出:

      Triangle
      Rectangle
      

      【讨论】:

        【解决方案4】:

        恕我直言,使用多态性的最佳示例是容器:

        std::vector<Shape*> shapes;
        shapes.push_back(new Triangle());
        shapes.push_back(new Rectangle());
        

        您可以遍历这个容器而不用关心对象的实际类型:

        for (int i=0;i<shapes.size();i++){
            shapes[i]->draw();
        }
        

        【讨论】:

          【解决方案5】:

          想象一个基类Shape。它公开了一个GetArea 方法。想象一个Square 类和一个Rectangle 类,以及一个Circle 类。无需创建单独的GetSquareAreaGetRectangleAreaGetCircleArea 方法,您只需在每个派生类中实现一个方法。您不必知道您使用的是Shape 的确切子类,只需调用GetArea 即可得到结果,与具体类型无关。

          看看这段代码:

          #include <iostream>
          using namespace std;
          
          class Shape
          {
          public:
            virtual float GetArea() = 0;
          };
          
          class Rectangle : public Shape
          {
          public:
            Rectangle(float a) { this->a = a; }
            float GetArea() { return a * a; }
          private:
            float a;
          };
          
          class Circle : public Shape
          {
          public:
            Circle(float r) { this->r = r; }
            float GetArea() { return 3.14f * r * r; }
          private:
            float r;
          };
          
          int main()
          {
            Shape *a = new Circle(1.0f);
            Shape *b = new Rectangle(1.0f);
          
            cout << a->GetArea() << endl;
            cout << b->GetArea() << endl;
          }
          

          这里需要注意的重要一点是 - 您不必知道您正在使用的类的确切类型,只需知道基本类型,您就会得到正确的结果。这在更复杂的系统中也非常有用。

          【讨论】:

          • 他已经有了一个带有draw方法的类层次结构。你能不解释一下吗?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-09-15
          • 1970-01-01
          • 1970-01-01
          • 2010-12-26
          • 1970-01-01
          • 1970-01-01
          • 2019-02-22
          相关资源
          最近更新 更多