多态如何使我的代码更灵活?

时间:2014-08-07 12:10:27

标签: oop polymorphism

我正在阅读首先面向对象的设计,以便更好地理解OOP概念。

多态性解释为:

Airplane plane = new Airplane();
Airplane plane = new Jet();
Airplane plane = new Rocket();

您可以编写适用于超类的代码,例如飞机,但可以使用任何子类。 : - *嗯.. .. 我得到了这个。*

进一步解释:

- > 那么多态如何使代码变得灵活?

  

好吧,如果您需要新功能,可以编写一个新的子类   飞机。但由于您的代码使用了超类,因此您的新类将起作用   没有对代码的其余部分进行任何更改。

现在我没有得到它。我需要创建一个飞机的子类。例如:我创建了一个类Randomflyer。要使用它,我将不得不创建它的对象。所以我会用:

Airplane plane = new Randomflyer();

我没有得到它。即使我会直接创建子类的对象。当我添加一个新的子类时,我仍然不需要在任何地方更改我的代码。如何使用超类来节省我对其余代码的额外更改?

11 个答案:

答案 0 :(得分:29)

说你有以下(简化):

Airplane plane = new MyAirplane();

然后用它做各种各样的事情:

List<Airplane> formation = ...
// superclass is important especially if working with collections
formation.add(plane);
// ...
plane.flyStraight();
plane.crashTest();
// ... insert some other thousand lines of code that use plane

事情是。当你突然决定将飞机改为

Airplane plane = new PterdodactylSuperJet();

我上面写的所有其他代码都可以正常工作(当然不同),因为其他代码依赖于通用 Airplane类提供的接口(读取:公共方法) ,而不是您在开始时提供的实际实施。通过这种方式,您可以传递不同的实现,而无需更改其他代码。

如果您没有使用Airplane超类,并且只是在替换

的意义上写了MyAirplanePterdodactylSuperJet
MyAriplane plane = new MyAirplane();

PterdodactylSuperJet plane = new PterdodactylSuperJet();

然后你有一个观点:你的代码的其余部分仍然可以工作。 但这恰好工作,因为你在两个类中都编写了相同的接口(公共方法),就是。如果您(或其他一些开发人员)更改一个类中的接口,在飞机类之间来回移动将使您的代码无法使用。

修改

故意我的意思是您专门在MyAirplanePterodactylSuperJet中实现具有相同签名的方法,以便您的代码能够正确运行。如果您或其他人更改了一个类的接口,您的灵活性就会被破坏。

实施例。假设您没有Airplane超类,而另一个毫无戒心的开发者会修改方法

public void flyStraight()

MyAirplane

public void flyStraight (int speed)

并假设您的plane变量属于MyAirplane类型。然后大代码需要一些修改;假设无论如何都需要。事情是,如果你回到PterodactylSuperJet(例如测试它,比较它,多种原因),你的代码就不会运行。 Whygodwhy 。因为您需要为PterodactylSuperJet提供您没有写过的方法flyStraight(int speed)。你可以这样做,你可以修理,没关系。

这是一个简单的场景。但是,如果

  • 这个问题在无辜的修改一年之后咬了你的屁股?你甚至可能会忘记为什么你这样做了。
  • 不是一个人,而是发生了大量修改,你无法跟踪?即使如果你可以跟踪,你需要让新课程加快速度。几乎从来都不容易,肯定从不愉快。
  • 而不是两个飞机班,你有一百个?
  • 以上的任何线性(或非)组合?

如果您编写了一个Airplane超类并使每个子类覆盖其相关方法,那么通过将flyStraight()更改为flyStraight(int)中的Airplane,您将被迫适应所有因此,子类,因此保持一致性。因此,灵活性不会改变。

结束修改

这就是为什么一个超级阶层会像某种“爸爸”一样留下来的原因。从某种意义上说,如果某人修改了它的界面,那么所有的子类都会跟随,因此你的代码会更加灵活。

答案 1 :(得分:12)

一个非常简单的用例来证明多态性的好处是对对象列表进行批处理而不必真正打扰它的类型(即将此责任委托给每个具体类型)。这有助于在一组对象上一致地执行抽象操作。

假设您要实施模拟飞行计划,您希望在该计划中飞行列表中存在的每种类型的飞机。你只需致电

for (AirPlane p : airPlanes) {
    p.fly();
}

每架飞机都知道如何自行飞行,而且在进行此呼叫时,您不必担心飞机的类型。对象行为的这种一致性是多态性给你的。

答案 2 :(得分:7)

假设您在Controller类的Planes中有方法,如

parkPlane(Airplane plane)

servicePlane(Airplane plane)

在您的计划中实施。它不会 BREAK 您的代码。 我的意思是,只要它接受AirPlane的参数,就不需要改变。

因为它会接受任何实际类型的飞机,flyerhighflyrfighter等等。

另外,在一个集合中:

List<Airplane> plane; //将带走你所有的飞机。

以下示例将清楚您的理解。


interface Airplane{
    parkPlane();
    servicePlane();
}

现在你有一架实现它的战斗机,所以

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

对于HighFlyer和其他clasess来说同样如此:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

现在多次使用AirPlane来考虑您的控制器类,

假设您的Controller类是AirPort,如下所示,

public Class AirPort{ 

AirPlane plane;

public AirPlane getAirPlane() {
    return airPlane;
}

public void setAirPlane(AirPlane airPlane) {
    this.airPlane = airPlane;
 }

}

魔法来自多态性使您的代码更加灵活,因为

您可以根据需要制作新的AirPlane类型实例并且不会更改

AirPort类的代码。

您可以根据需要设置AirPlane实例(这也称为依赖项Intection)..

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

现在想想如果您创建新类型的平面,或者删除任何类型的平面,它会对您的AirPort产生影响吗?

不,因为我们可以说AirPort类以多态方式引用AirPlane

答案 3 :(得分:7)

其他人更全面地解决了关于多态性的问题,但我想回答一个具体的问题:

  

我没有得到它,即使我会创建一个子类的对象   直接

这实际上是一个大问题,人们为避免这样做而付出了很多努力。如果你打开像Gang of Four这样的东西,那么有很多模式专门用来避免这个问题。

主要方法称为工厂模式。看起来像这样:

AirplaneFactory factory = new AirplaneFactory();

Airplane planeOne = factory.buildAirplane();
Airplane planeTwo = factory.buildJet();
Airplane planeThree = factory.buildRocket();

通过抽象出对象的实例化,这为您提供了更大的灵活性。您可能会想到这样的情况:您的公司开始主要构建Jet,因此您的工厂有一个buildDefault()方法,如下所示:

public Airplane buildDefault() {
    return new Jet();
}

有一天,你的老板走近你并告诉你业务已经改变了。这些天人们真正想要的是Rocket s - Jet s已成为过去。

如果没有AirplaneFactory,您就必须查看代码并将new Jet()的{​​{1}}替换为new Rocket()。使用Factory模式,您可以进行如下更改:

public Airplane buildDefault() {
    return new Rocket();
}

所以变化的范围大大减少了。由于您已经编码到界面Airplane而不是具体类型JetRocket,因此这只是您需要进行的更改

答案 4 :(得分:6)

据我了解,其优势在于,例如,在飞机格斗游戏中,您必须在每个循环中更新所有飞机的位置,但您有几种不同的飞机。假设你有:

  • 米格21
  • Waco 10
  • Mitsubishi Zero
  • Eclipse 500
  • 幻影

你不想像以下那样单独更新他们的动作和位置:

Mig21 mig = new Mig21();
mig.move();
Waco waco = new Waco();
waco.move();
Mitsubishi mit = new Mitsubishi();
mit.move();
...

你想拥有一个可以接受任何子类(Airplane)并在循环中全部更新的超类:

airplaneList.append(new Mig21());
airplaneList.append(new Waco());
airplaneList.append(new Mitsubishi());
...
for(Airplane airplane : airplanesList)
    airplane.move()

这使您的代码更加简单。

答案 5 :(得分:5)

完全正确的是,子类仅对实例化它们的人有用。 Rich Hickey总结了这一点:

  

......任何新阶级本身都是一个岛屿;任何人在任何地方写的任何现有代码都无法使用。所以考虑用洗澡水把婴儿扔掉。

仍然可以使用已在其他地方实例化的对象。作为一个简单的例子,任何接受类型&#34;对象&#34;的参数的方法。可能会给出一个子类的实例。

但是还有另一个问题,那就是更微妙。 通常子类(如Jet)将无法使用代替父类(如Airplane)。假设子类与父类可互换是造成巨大错误数量的原因。

这种可互换性属性称为 Liskov替代原则,最初表述为:

  

设q(x)是关于类型T的对象x可证明的属性。那么对于S类型的对象y,q(y)应该是可证明的,其中S是T的子类型。

在您的示例中,T是Airplane类,S是Jet类,x是Airplane实例,y是Jet实例。

&#34;属性&#34; q是实例的结果&#39;方法,它们的属性的内容,将它们传递给其他操作者或方法的结果等。我们可以想到&#34;可证明的&#34;作为意义&#34;可观察&#34 ;;即。如果两个对象实现不同,如果它们的结果没有差异,则无关紧要。同样,如果两个对象在无限循环后表现不同,那也无关紧要,因为永远无法访问该代码。

将Jet定义为飞机的子是一个微不足道的语法问题:Jet的声明必须包含extends Airplane令牌,并且不能包含final令牌飞机声明中的instanceof令牌。编译器检查对象是否遵守子类的规则是微不足道的。但是,这并没有告诉我们Jet是否是飞机的子;即。 Jet是否可用于代替飞机。 Java将允许它,但这并不意味着它将起作用。

我们可以让Jet成为飞机的子类型的一种方法是让Jet成为一个空类;它的所有行为都来自飞机。然而,即使是这个微不足道的解决方案也存在问题:飞机和普通Jet在传递给instanceof运算符时的行为会有所不同。因此,我们需要检查使用飞机的所有代码,以确保没有instanceof次调用。当然,这完全违背了封装和模块化的思想;我们无法检查甚至可能不存在的代码!

通常我们想要子类,以便为超类做一些不同的事情。在这种情况下,我们必须确保使用飞机的任何代码都不会观察到这些差异。这比语法检查{{1}}更难。我们需要知道所做的所有代码

由于Rice的定理,这是不可能的,因此无法自动检查子类型,因此导致错误的数量。

由于这些原因,许多人认为子类多态是一种反模式。还有其他形式的多态性不会遇到这些问题,例如&#34;参数多态性&#34; (简称&#34;泛型&#34;在Java中)。

Liskov Substitution Principle

Comparison between sub-classing and sub-typing

Parameteric polymorphism

Arguments against sub-classing

Rice's theorem

答案 6 :(得分:4)

多态性何时有用的一个很好的例子:

我们假设您有抽象类Animal,它定义了所有动物共有的方法,例如makeNoise()

然后,您可以使用DogCatTiger等子类扩展它。

这些动物中的每一个都会覆盖抽象类的方法,例如makeNoise(),以使这些行为特定于其类。这很好,因为每只动物都会发出不同的声音。

以下是多态性是一件好事的一个例子:集合。

假设我有一个ArrayList<Animal> animals,它中充满了几种不同的动物。

多态性使这段代码成为可能:

for(Animal a: animals) 
{
    a.makeNoise();
}

因为我们知道每个子类都有makeNoise()方法,所以我们可以相信这会导致每个动物对象调用 makeNoise()的特定版本 (例如,狗吠,猫喵,牛mo,所有没有你甚至不必担心哪种动物做什么。

与项目团队合作时,另一个优势显而易见。让我们说另一位开发人员在没有告诉你的情况下添加了几种新动物,而且你有一系列动物现在拥有一些这些新的动物类型(你甚至不知道它们存在!)。您仍然可以调用makeNoise()方法(或动物超类中的任何其他方法)并相信每种类型的动物都知道该怎么做。

这个动物超类的优点在于你可以扩展一个超类并根据需要制作尽可能多的新动物类型,而无需更改超类中的任何内容或破坏任何代码。

记住多态的黄金法则。您可以在期望超类类型对象的任何位置使用子类。

例如:

Animal animal = new Dog;

学习多态思考需要一段时间,但一旦你学会了你的代码就会有很大的改进。

答案 7 :(得分:3)

多态性源于遗传。整个想法是你有一个普通的基类和更具体的派生类。然后,您可以编写与基类一起使用的代码...而且polymorphims使您的代码不仅可以使用基类,还可以使用所有派生类。

如果您决定让您的超级班级有一个方法,比如getPlaneEngineType(),那么您创建一个新的子类“Jet from inherits from Plane”Plane jet = new Jet()将/仍然可以访问超类的getPlaneEngineType。虽然您仍然可以使用超级调用编写自己的getJetEngineType()来基本覆盖超类的方法,这意味着您可以编写可以使用任何“平面”的代码,而不仅仅是使用Plane或Jet或BigFlyer。

答案 8 :(得分:3)

多态性是强大的,因为当需要更改行为时,您可以通过覆盖方法来更改它。

您的超类将其属性和行为继承到由其扩展的子类。因此,隐式地转换类型也来自其超类的对象是安全的。子类的这些常用方法使它们对实现API很有用。有了它,多态性使您能够扩展代码的功能。

答案 9 :(得分:3)

我不认为这是一个很好的例子,因为它似乎混淆了本体和多态。

你必须问自己,“Jet&#39;”行为的哪个方面?不同于飞机&#39;这会使软件复杂化以使用不同的子类型进行建模是合理的吗?这本书的预览在一页进入示例后会中断,但似乎没有任何理由来设计。总是问自己行为是否存在差异,而不是仅仅添加类来对事物进行分类 - 通常使用属性值或编写策略比使用子类更好。

一个例子(简化自我早期领导的一个主要项目)将是飞机是最终的,但具有抽象类型的各种属性,其中一个是引擎。有多种计算发动机的推力和燃料使用的方法 - 用于快速喷射双三次插值表的推力和燃油率对马赫和油门(以及有时压力和湿度)的值,对于火箭队的表法,但没有要求赔偿使发动机进气口的空气停转;对于道具来说,一个更简单的参数化&#39; bootstrap&#39;方程可以使用。因此,您将拥有三类AbstractAeroEngine - JetEngine,RocketEngine和BootstrapEngine,这些方法可以实现在给定油门设置和当前马赫数的情况下返回推力和燃油使用率的方法。 (你几乎不应该将非抽象类型子类型化)

请注意,AbstractAeroEngine类型之间的差异虽然与不同的真实世界引擎有关,但它们在软件计算引擎的推力和燃料使用方面完全不同 - 您没有构建类的本体它描述了现实世界的视图,但专注于软件中执行的操作以适应特定的用例。

  

使用超类如何使我免于对其余代码进行额外更改?

由于您的所有发动机计算都是多态的,这意味着当您创建飞机时,您可以使用任何适合它的发动机推力计算。如果你发现你必须提供另一种计算推力的方法(正如我们所做的那样),那么你可以添加另一种子类型的AeroEngine - 只要它提供的实施提供了信任和燃料费率,那么其余的该系统并不关心内部差异 - 飞机类仍然会询问其发动机的推力。飞机只关心它有一个引擎,它可以使用与任何其他引擎相同的方式,只有创建代码必须知道引擎的类型,并且ScramJetEngine的实现只关心超音速喷射计算 - 计算升力和阻力的飞机部件以及飞行的策略都不必改变。

答案 10 :(得分:1)

多态性获得属性以及超类的所有行为和接口。一架飞机的行为与飞机的行为完全相同吗?