多态的真正意义是什么?(用途)

28

我对面向对象编程(OOP)很陌生。虽然我理解什么是多态(polymorphism),但我无法理解它的实际用途。我可以使用不同名称的函数。为什么我要尝试在我的应用程序中实现多态?


1
请查看:https://dev59.com/8nRC5IYBdhLWcg3wG9Jb - Zaki
1
我试图在https://dev59.com/eG025IYBdhLWcg3wsIIU#5854862上回答这个问题 - 尽管这主要关注C ++对多态的支持,其他语言可能具有提到的设施的子集或超集。不过我认为它相当好地涵盖了这个概念...希望能有所帮助。 - Tony Delroy
10个回答

24

经典回答:想象一个基类 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;
}

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

学习愉快!


我认为矩形/圆形是一个很差的例子,因为它自然地导致了经典的“椭圆问题”。假设你有一个矩形类,可以设置宽度和高度,然后有一个从矩形扩展而来的正方形类,它同时设置宽度和高度。然后,rect.Width *= 2; rect.Height *= 2; 实际上给你的是4倍的边长。 - Warty
@Warty 如果想进一步了解,请搜索Liskov替换原则。 - Laurynas Lazauskas
我能否只定义 Circle *a = new Circle(1.0f); 然后调用 a->GetArea()?为什么我们不能这样做?在什么情况下必须使用基础指针,即 Shape *ptr?谢谢。 - skytree

15

你有没有用+把两个整数相加,再用+把一个整数和一个浮点数相加?

你有没有记录x.toString()来帮助你调试某些东西?

我认为你可能已经欣赏多态性,只是不知道它的名字。


10
在强类型语言中,多态性很重要,因为它能使我们拥有不同类型对象的列表/集合/数组。这是因为列表/数组本身具有包含正确类型对象的类型。
例如,想象一下以下情况:
// the following is pseudocode M'kay:
class apple;
class banana;
class kitchenKnife;

apple foo;
banana bar;
kitchenKnife bat;

apple *shoppingList = [foo, bar, bat]; // this is illegal because bar and bat is
                                       // not of type apple.

解决方法:

class groceries;
class apple inherits groceries;
class banana inherits groceries;
class kitchenKnife inherits groceries;

apple foo;
banana bar;
kitchenKnife bat;

groceries *shoppingList = [foo, bar, bat]; // this is OK

此外,它使得处理项目列表更加直观。比如说所有杂货都实现了方法price(),这样处理起来就很容易:

int total = 0;
foreach (item in shoppingList) {
    total += item.price();
}

这两个特性是多态所做的核心。


5

多态的优点是客户端代码不需要关心方法的实际实现。 看下面的例子。 这里的CarBuilder不知道任何关于ProduceCar()的信息。一旦它被给定一个汽车列表(CarsToProduceList),它将生产所有必要的汽车。

class CarBase
{
    public virtual void ProduceCar()
    {
        Console.WriteLine("don't know how to produce");
    }
}

class CarToyota : CarBase
{
    public override void ProduceCar()
    {
        Console.WriteLine("Producing Toyota Car ");
    }
}

class CarBmw : CarBase
{
    public override void ProduceCar()
    {
        Console.WriteLine("Producing Bmw Car");
    }
}

class CarUnknown : CarBase { }

class CarBuilder
{
    public List<CarBase> CarsToProduceList { get; set; }

    public void ProduceCars()
    {
        if (null != CarsToProduceList)
        {
            foreach (CarBase car in CarsToProduceList)
            {
                car.ProduceCar();// doesn't know how to produce
            }
        }

    }
}

class Program
{
    static void Main(string[] args)
    {
        CarBuilder carbuilder = new CarBuilder();
        carbuilder.CarsToProduceList = new List<CarBase>() { new CarBmw(), new CarToyota(), new CarUnknown() };            
        carbuilder.ProduceCars();
    }
}

4

多态是面向对象编程的基础。它意味着一个对象可以作为另一个项目存在。那么一个对象如何变成另一个对象呢?通过以下方式实现:

  1. 继承
  2. 覆盖/实现父类行为
  3. 运行时对象绑定

其中一个主要优点是可以切换实现。比如你正在编写一个需要与数据库交互的应用程序,你定义了一个类来执行这些数据库操作,它被期望执行某些操作,例如添加、删除和修改。你知道数据库可以以许多方式实现,可以是与文件系统交互,也可以是与诸如MySQL等的RDBM服务器交互。因此,作为程序员,你会定义一个接口,例如...

public interface DBOperation {
    public void addEmployee(Employee newEmployee);
    public void modifyEmployee(int id, Employee newInfo);
    public void deleteEmployee(int id);
}

现在你可能有多种实现方式,比如说我们有一种针对关系型数据库的实现,另一种是针对直接文件系统的实现。

public class DBOperation_RDBMS implements DBOperation
    // implements DBOperation above stating that you intend to implement all
    // methods in DBOperation
    public void addEmployee(Employee newEmployee) {
          // here I would get JDBC (Java's Interface to RDBMS) handle
          // add an entry into database table.
    }
    public void modifyEmployee(int id, Employee newInfo) {
          // here I use JDBC handle to modify employee, and id to index to employee
    }
    public void deleteEmployee(int id) {
          // here I would use JDBC handle to delete an entry
    }
}

让我们实现一个文件系统数据库。
public class DBOperation_FileSystem implements DBOperation
    public void addEmployee(Employee newEmployee) {
          // here I would Create a file and add a Employee record in to it
    }
    public void modifyEmployee(int id, Employee newInfo) {
          // here I would open file, search for record and change values
    }
    public void deleteEmployee(int id) {
          // here I search entry by id, and delete the record
    }
}

让我们看看主函数如何在两者之间切换。
public class Main {
    public static void main(String[] args) throws Exception {
          Employee emp = new Employee();
          ... set employee information

          DBOperation dboper = null;
          // declare your db operation object, not there is no instance
          // associated with it

          if(args[0].equals("use_rdbms")) {
               dboper = new DBOperation_RDBMS();
               // here conditionally, i.e when first argument to program is
               // use_rdbms, we instantiate RDBM implementation and associate
               // with variable dboper, which delcared as DBOperation.
               // this is where runtime binding of polymorphism kicks in
               // JVM is allowing this assignment because DBOperation_RDBMS
               // has a "is a" relationship with DBOperation.
          } else if(args[0].equals("use_fs")) {
               dboper = new DBOperation_FileSystem(); 
               // similarly here conditionally we assign a different instance.
          } else {
               throw new RuntimeException("Dont know which implemnation to use");
          }

          dboper.addEmployee(emp);
          // now dboper is refering to one of the implementation 
          // based on the if conditions above
          // by this point JVM knows dboper variable is associated with 
          // 'a' implemenation, and it will call appropriate method              
    }
}

您可以在许多地方使用多态的概念,一个实际的例子是:假设您正在编写图像装饰器,并且需要支持各种图像类型,例如jpg、tif、png等。因此,您的应用程序将定义一个接口并直接对其进行操作。然后,您将为每个jpg、tif、png等实现提供一些运行时绑定。

另一个重要的用途是,如果您使用Java,大多数情况下您都会使用List接口,这样您就可以在需要时使用ArrayList或其他接口,以满足您的应用程序增长或需求变化的要求。


好的!这正是我在上面回复中所指的。它有助于动态调用正确的函数。 - Kapil D

1

多态性允许您编写使用对象的代码。然后,您可以稍后创建新类,您现有的代码可以在不进行任何修改的情况下使用。

例如,假设您有一个函数Lib2Groc(vehicle),它将车辆从图书馆指导到杂货店。它需要告诉车辆向左转,因此它可以在车辆对象上调用TurnLeft()等其他操作。然后,如果有人后来发明了一种新型车辆,比如气垫船,它可以被Lib2Groc使用而无需进行任何修改。


1

我想有时候对象是动态调用的。在经典的形状多边形的例子中,您不确定对象是否为三角形、正方形等。

因此,为了摆脱所有这些问题,我们只需调用派生类的函数,并假设会调用动态类之一。

您不需要关心它是正方形、三角形还是矩形。您只需要关心面积。因此,根据传递的动态对象,将调用getArea方法。


1

多态操作最显著的好处之一是能够扩展功能。 您可以使用相同的操作,而不更改现有的接口和实现,只因为您需要一些新的东西。

我们从多态性中想要的一切 - 就是简化我们的设计决策,使我们的设计更具可扩展性和优雅性。 您还应该注意开闭原则(http://en.wikipedia.org/wiki/Open/closed_principle)和SOLID(http://en.wikipedia.org/wiki/Solid_%28Object_Oriented_Design%29),这些可以帮助您理解关键的面向对象原则。

P.S. 我认为您在谈论“动态多态性”(http://en.wikipedia.org/wiki/Dynamic_polymorphism),因为还有“静态多态性”(http://en.wikipedia.org/wiki/Template_metaprogramming#Static_polymorphism)这样的东西。


0

你不需要多态。

直到你需要它。

然后它太棒了。

一个你将会遇到很多次的简单答案:

有人需要遍历一些东西的集合。比如他们要求一个类型为MySpecializedCollectionOfAwesome的集合。但你一直在处理类型为List<T>的Awesome实例。所以,现在,你需要创建一个MSCOA的实例,并用你List<T>中所有Awesome实例来填充它。这真让人头疼,对吧?

但是,如果他们要求一个IEnumerable<Awesome>,你就可以给他们很多个Awesome的集合。比如你可以给他们一个数组(Awesome[])或一个列表(List<Awesome>)或一个ObservableCollection of Awesome或其他任何你保存你的Awesome并且实现了IEnumerable<T>接口的东西。

多态的威力使你能够保持类型安全,同时又足够灵活,让你可以使用一个实例进行许多不同的操作,而不必创建特别处理此类型或那个类型的大量代码。


0

选项卡应用程序

对我来说,一个好的应用程序是在选项卡应用程序中具有通用按钮(适用于所有选项卡)- 即使我们使用的浏览器也实现了多态性,因为它不知道我们在编译时(换句话说,在代码中)使用的选项卡。它总是在运行时(就是现在!当我们使用浏览器时。)确定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接