为什么要使用多态性?

41

我有以下的代码,其中包含了一个父类和它的子类。我尝试确定在使用多态性方面这段代码的好处。

class FlyingMachines {
    public void fly() {
        System.out.println("No implementation");
    }
}

class Jet extends FlyingMachines {
    public void fly() {
        System.out.println("Start, Taxi, Fly");
    }

    public void bombardment() {
        System.out.println("Throw Missile");
    }
}

public class PolymorphicTest {
    public static void main(String[] args) {
        FlyingMachines flm = new Jet();
        flm.fly();

        Jet j = new Jet();
        j.bombardment();
        j.fly();
    }
}

flm.fly()j.fly()给我同样的答案时,多态的优点是什么?


1
我认为他想知道flm.fly()和j.fly()之间有什么区别?如果有的话,是什么? - khan
这里需要区分编译时运行时之间的区别... Java编译器将看到flm.fly();评估为:System.out.println("No implementation");... Java虚拟机在运行时将看到flm.fly();评估为:System.out.println("Start, Taxi, Fly");... 如果您将第20行从flm.fly();更改为flm.bombardment();,则会出现编译错误,因为bombardment()消息不是FlyingMachines协议的一部分。 - Jack_Hu
13个回答

57

在您的例子中,使用多态并不是非常有帮助的,因为您只有一个 FlyingMachine 的子类。如果您有多种类型的 FlyingMachine,那么多态就会变得很有用。然后,您可以有一个接受任何类型的 FlyingMachine 的方法,并使用它的 fly() 方法。一个例子可能是 testMaxAltitude(FlyingMachine)

另一个仅在多态下可用的功能是能够拥有一个 List<FlyingMachine> 并将其用于存储 JetKiteVerySmallPebbles

使用多态最好的理由之一是能够引用接口而不是实现。

例如,最好有一个返回 List<FlyingMachine> 而不是 ArrayList<FlyingMachine> 的方法。这样,我可以在方法中更改我的实现为 LinkedListStack,而不会破坏使用我的方法的任何代码。


4
+1,但是更相关的例子来展示其有用性可能是使用List<FlyingMachine>而不是List<Jet> - cheeken
@cheeken,我在你发帖后更新了我的回答。请看我的更新。 - Tim Pote

31

flm.fly()j.fly() 给出相同答案时,多态性的优点是:

它的优点在于

FlyingMachines flm = new Jet();
flm.fly();

返回

"Start, Taxi, Fly"

取代

"No implementation"

那就是多态。你在类型为FlyingMachine的对象上调用fly(),它仍然知道自己实际上是一个Jet,并调用适当的fly()方法而不是错误的方法,输出"No implementation"

这意味着你可以编写能够处理FlyingMachine类型对象并提供各种子类型,如JetHelicopter的方法,并且这些方法将始终执行正确的操作,即调用适当类型的fly()方法,而不是一直做同样的事情,即输出"No implementation"

多态性

在你的例子中,多态性并没有什么用处。

  • a) 当你有不同类型的对象并且可以编写能够使用所有这些不同类型的类时,它才有用,因为它们都遵循相同的API。

  • b) 当你可以向应用程序添加新的FlyingMachine时而不更改任何现有逻辑时,它也很有用。

a) 和 b) 是同一枚硬币的两面。

让我来示范一下。

代码示例

import java.util.ArrayList;
import java.util.List;

import static java.lang.System.out;

public class PolymorphismDemo {

    public static void main(String[] args) {
        List<FlyingMachine> machines = new ArrayList<FlyingMachine>();
        machines.add(new FlyingMachine());
        machines.add(new Jet());
        machines.add(new Helicopter());
        machines.add(new Jet());

        new MakeThingsFly().letTheMachinesFly(machines);
    }
}

class MakeThingsFly {
    public void letTheMachinesFly(List<FlyingMachine> flyingMachines) {
        for (FlyingMachine flyingMachine : flyingMachines) {
            flyingMachine.fly();
        }
    }
}

class FlyingMachine {
    public void fly() {
        out.println("No implementation");
    }
}

class Jet extends FlyingMachine {
    @Override
    public void fly() {
        out.println("Start, taxi, fly");
    }

    public void bombardment() {
        out.println("Fire missile");
    }
}

class Helicopter extends FlyingMachine {
    @Override
    public void fly() {
        out.println("Start vertically, hover, fly");
    }
}

解释

a) MakeThingsFly 类可以处理所有类型为 FlyingMachine 的对象。

b) 当你添加一个新的类,如 PropellerPlane 时,letTheMachinesFly 方法也可以不做任何更改地使用!

public void letTheMachinesFly(List<FlyingMachine> flyingMachines) {
        for (FlyingMachine flyingMachine : flyingMachines) {
            flyingMachine.fly();
        }
    }
}

这就是多态的威力。你可以用它实现开放-封闭原则


讲解得非常清晰明了。谢谢! - Neha Chaudhary
很高兴听到这个好消息,@NehaChaudhary! - Lernkurve

15
使用多态的原因是当你构建一个能够接受许多不同对象但具有相同接口的通用框架时。当你创建新类型的对象时,只要它遵循对象的"规则",就无需更改框架以适应新的对象类型。
所以在你的情况下,一个更有用的例子是创建一个名为"Airport"的对象类型,它接受不同类型的FlyingMachines。机场将定义一个"AllowPlaneToLand"函数,类似于:
//pseudocode
void AllowPlaneToLand(FlyingMachine fm)
{
    fm.LandPlane();
}
只要每种FlyingMachine定义了适当的LandPlane方法,它们就可以正确地降落。机场不需要了解FlyingMachine的任何信息,除了要在飞机降落时调用FlyingMachine上的LandPlane方法。因此,机场不再需要更改,并且可以继续接受新类型的FlyingMachines,无论是手滑翔机、UFO、降落伞等等。

因此,多态性对于围绕这些对象构建的框架非常有用,这些框架可以通用地访问这些方法而无需进行更改。


10

首先让我们看一下面向对象设计,继承表示 IS-A 关系,通常我们可以说类似于 "让我们的FlyingMachines飞起来"。每一个特定的FlyingMachines(子类)都是 FlyingMachines(父类),比如说Jet就适用于“让我们的FlyingMachines飞起来”,而我们希望这个飞行实际上是特定子类(子类)的飞行函数,这就是多态性所实现的。

因此,我们以抽象的方式进行操作,基于接口和基类,不会依赖于具体的实现细节,多态性将完成正确的事情!


6

多态(包括运行时和编译时)在Java中有很多必要的用途。

方法重写是运行时多态,而方法重载是编译时多态

其中一些原因已经讨论过,其他原因如下:

  1. Collections: Suppose you have multiple type of flying machines and you want to have them all in a single collection. You can just define a list of type FlyingMachines and add them all.

    List<FlyingMachine> fmList = new ArrayList<>();
    fmList.add(new new JetPlaneExtendingFlyingMachine());
    fmList.add(new PassengerPlanePlaneExtendingFlyingMachine());
    

    The above can be done only by polymorphism. Otherwise you would have to maintain two separate lists.

  2. Caste one type to another : Declare the objects like :

    FlyingMachine fm1 = new JetPlaneExtendingFlyingMachine();
    FlyingMachine fm2 = new PassengerPlanePlaneExtendingFlyingMachine();
    fm1 = fm2; //can be done
    
  3. Overloading: Not related with the code you gave, But overloading is also another type of polymorphism called compile time polymorphism.

  4. Can have a single method which accepts type FlyingMachine handle all types i.e. subclasses of FlyingMachine. Can only be achieved with Polymorphism.


4

如果你只有喷气式飞机,那么这并没有增加太多,当你有不同的飞行器时就会体现出优势,例如:飞机

现在你已经修改了以包含更多类,多态性的优势在于将实例的具体类型(和业务概念)抽象出来,你只需要关心它能飞即可。


谢谢@Sebastian Piu - 让我再添加两个子类 1) 直升机 2) 飞机 - 所以我知道我的问题仍然是一样的。 - baig772

4

多态性还可以帮助我们的代码消除“if”条件语句,这是为了生成生产级别的代码,因为删除条件语句将增加代码的可读性,并帮助我们编写更好的单元测试用例,我们知道对于“n”个if情况,会有n!(n阶乘)种可能性。

让我们看看如何做到这一点。如果您有一个FlyingMachine类,它在构造函数中接受一个字符串来定义FlyMachine的类型,如下所示:

class FlyingMachine{

    private type;

    public FlyingMachine(String type){
             this.type = type;
    }

    public int getFlyingSpeedInMph {
         if(type.equals("Jet"))
              return 600;
         if(type.equals("AirPlane"))
              return 300;
    }

 }

我们可以创建两个 FlyingMachine 实例,如下所示:
 FlyingMachine jet = new FlyingMachine("Jet");
 FlyingMachine airPlane = new FlyingMachine("AirPlane");

并使用以下代码获取速度

 jet.fylingSpeedInMph();
 airPlane.flyingSpeedInMph();

但是,如果您使用多态性,您将通过扩展通用的FlyMachine类并覆盖getFlyingSpeedInMph方法来消除if条件,如下所示:

class interface FlyingMachine {
     
     public int abstract getFlyingSpeedInMph;
}

 
class Jet extends FlyingMachine {
    @Override
    public int getFlyingSpeedInMph(){
          return 600;
    }
}
                                   
class Airplane extends FlyingMachine {
    @Override
    public int getFlyingSpeedInMph(){
          return 300;
    }
}

现在您可以获取以下飞行速度:

 FlyingMachine jet = new Jet();
 jet.flyingSpeed();

 FlyingMachine airPlane = new AirPlane();
 airPlane.flyingSpeed();

3

让我们在这个例子中再添加一个类,它可以帮助您理解多态的使用...

class FlyingMachines {
    public void fly() {
        System.out.println("No implementation");
    }
}

class Jet extends FlyingMachines {
    public void fly() {
        System.out.println("Start, Jet, Fly");
    }
}

class FighterPlan extends FlyingMachines {
    public void fly() {
        System.out.println("Start, Fighter, Fight");
    }
}

public class PolymorphicTest {
    public static void main(String[] args) {
        FlyingMachines flm = new Jet();
        flm.fly();

        FlyingMachines flm2 = new FighterPlan();
        flm2.fly();
    }
}

输出:

Start, Jet, Fly
Start, Fighter, Fight

3

由于实例的类型实际上是相同的,即Jet,因此flm.fly()j.fly()都会给出相同的答案,它们的行为是相同的。

当您执行以下操作时,可以看到区别:

FlyingMachines flm = new FlyingMachines();
flm.fly();

Jet j = new Jet();
j.bombarment();
j.fly();

多态性是指具有相同方法签名但行为不同的特性。如您所见,FlyingMachinesJet都定义了fly()方法,但是这个方法的实现方式是不同的,因此被视为行为不同。

参见


1
不,覆盖是指具有不同行为的相同方法签名。多态性是更广泛的视角,本质上包括构造一个类型的对象,并将其视为另一种类型(即将Jet视为FlyingMachine)。继承、覆盖和重载是允许您实现有效多态性的工具(覆盖和重载是所谓的临时多态性)。 - Matt

1
多态性只有在需要时才会带来好处。当你的概念项目中的实体可以被视为另一个实体的专业化时,它就会被使用。主要思想是“专业化”。一个很好的例子就是所谓的分类法,例如应用于生物。狗和人都是哺乳动物。这意味着,类哺乳动物将所有具有共同属性和行为的实体分组在一起。
此外,电动车和柴油车都是汽车的专业化。因此,两者都有一个isThereFuel(),因为当你开车时,你希望知道是否有足够的燃料来驾驶它。另一个伟大的概念是“期望”。
在开始软件之前,绘制领域的ER(实体关系)图表总是一个好主意。这是因为你被迫描绘出将要创建的实体类型,并且如果你足够能干,你可以通过找到实体之间的共同行为来节省大量代码。但节省代码并不是一个好项目的唯一好处。
你可能对“软件工程”感兴趣,它是一组技术和概念的集合,可让您编写“清晰代码”(还有一本名为“Clean code”的好书,被程序员广泛推荐)。

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