我正在阅读Head First面向对象设计,以便更好地理解OOP概念.
多态性解释为:
Airplane plane = new Airplane(); Airplane plane = new Jet(); Airplane plane = new Rocket();
您可以编写适用于超类的代码,如飞机,但可以使用任何子类.: - 嗯...... 我得到了这个.*.
它进一步解释说:
- > 那么多态如何使代码变得灵活?
好吧,如果你需要新的功能,你可以写一个新的AirPlane子类.但是,由于您的代码使用了超类,因此您的新类将在不对代码的其余部分进行任何更改的情况下工作.
现在我没有得到它.我需要创建一个飞机的子类.例如:我创建一个类,Randomflyer
.要使用它,我将不得不创建它的对象.所以我会用:
Airplane plane = new Randomflyer();
我没有得到它.即使我会直接创建子类的对象.当我添加新的子类时,我仍然不需要在任何地方更改我的代码.使用超类如何使我免于对其余代码进行额外更改?
假设您有以下(简化):
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
超类,只是写了,MyAirplane
并且PterdodactylSuperJet
在你替换的意义上
MyAriplane plane = new MyAirplane();
同
PterdodactylSuperJet plane = new PterdodactylSuperJet();
那么你有一个观点:你的代码的其余部分仍然可以工作.但这恰好起作用,因为你故意在两个类中编写了相同的接口(公共方法).如果您(或其他一些开发人员)更改一个类中的接口,在飞机类之间来回移动将使您的代码无法使用.
编辑
通过有目的我的意思是你专门实现与在两个相同签名的方法MyAirplane
,并PterodactylSuperJet
在为了使您的代码与这两个正常运行.如果您或其他人更改了一个类的接口,您的灵活性就会被破坏.
例.假设您没有Airplane
超类,而另一个不知情的开发者修改了该方法
public void flyStraight()
在MyAirplane
以
public void flyStraight (int speed)
并假设您的plane
变量是类型MyAirplane
.然后大代码需要一些修改; 假设无论如何都需要.事情是,如果你回到a PterodactylSuperJet
(例如测试它,比较它,过多的原因),你的代码将无法运行.Whygodwhy.因为你需要提供你没有写PterodactylSuperJet
的方法flyStraight(int speed)
.你可以这样做,你可以修理,没关系.
这是一个简单的场景.但是如果
在无辜的修改一年后,这个问题咬了你的屁股?你甚至可能会忘记为什么你这样做了.
不是一个,而是发生了大量修改,你无法跟踪?甚至如果你可以保持跟踪,你需要得到新的类加快速度.几乎从不轻松,绝对不会令人愉快.
而不是两个平面类,你有一百个?
以上的任何线性(或不是)组合?
如果您编写了一个Airplane
超类并使每个子类覆盖其相关方法,那么通过更改flyStraight()
为flyStraight(int)
in,Airplane
您将被迫相应地调整所有子类,从而保持一致性.因此灵活性不会改变.
结束编辑
这就是为什么一个超类保持某种"爸爸"的意思,如果某人修改了它的界面,所有的子类都会跟随,因此你的代码会更灵活.
假设您在Controller类的Planes中有方法
parkPlane(Airplane plane)
和
servicePlane(Airplane plane)
在你的程序中实现.它不会破坏您的代码.我的意思是,只要它接受参数,它就不需要改变AirPlane
.
因为它会接受任何飞机,尽管实际类型的,flyer
,highflyr
,fighter
,等.
另外,在一个集合中:
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
什么影响吗?
不,因为我们可以说The AirPort
class引用了AirPlane
多态.
一个非常简单的用例来证明多态性的好处是批处理对象列表而不必真正打扰它的类型(即将此责任委托给每个具体类型).这有助于在对象集合上一致地执行抽象操作.
假设你想要实现一个模拟飞行计划,你想要飞行列表中存在的每种类型的飞机.你只需打电话
for (AirPlane p : airPlanes) { p.fly(); }
每架飞机都知道如何自行飞行,在进行此调用时您无需担心飞机的类型.对象行为的这种一致性是多态性给你的.
您完全正确的是,子类仅对实例化它们的人有用.Rich Hickey很好地总结了这一点:
......任何新阶级本身都是一个岛屿; 任何人在任何地方写的任何现有代码都无法使用.所以考虑用洗澡水把婴儿扔出去.
仍然可以使用已在其他地方实例化的对象.作为一个简单的例子,任何接受"Object"类型的参数的方法都可能被赋予一个子类的实例.
但是还有另一个问题,那就是更微妙.通常,子类(如Jet)不能代替父类(如飞机).假设子类可以与父类互换,这是导致大量错误的原因.
这种可互换性被称为Liskov替代原则,最初表述为:
设q(x)是关于类型T的对象x可证明的属性.那么对于类型S的对象y,q(y)应该是可证明的,其中S是T的子类型.
在您的示例的上下文中,T是Airplane类,S是Jet类,x是Airplane实例,y是Jet实例.
"属性"q是实例方法的结果,它们的属性的内容,将它们传递给其他运算符或方法的结果等.我们可以将"可证明"视为"可观察"的含义; 即.如果两个对象的实现方式不同,那么它们的结果没有区别.同样,无限循环后两个对象的行为是否无关紧要,因为永远不会达到该代码.
将Jet定义为飞机的子类是一个微不足道的语法问题:Jet的声明必须包含extends Airplane
令牌,并且final
飞机声明中不得有令牌.编译器检查对象是否遵守子类的规则是微不足道的.然而,这并没有告诉我们Jet是飞机的子类型 ; 即.Jet是否可用于代替飞机.Java将允许它,但这并不意味着它将起作用.
我们可以让Jet成为飞机的一种子类型的方法是让Jet成为一个空类; 它的所有行为都来自飞机.然而,即使是这个微不足道的解决方案也存在问题:飞机和普通喷气机在传递给instanceof
操作员时表现不同.因此,我们需要检查使用飞机的所有代码,以确保没有instanceof
呼叫.当然,这完全违背了封装和模块化的思想; 我们无法检查甚至可能不存在的代码!
通常我们想要子类,以便对超类做一些不同的事情.在这种情况下,我们必须确保使用飞机的任何代码都不会观察到这些差异.这比语法检查更难instanceof
; 我们需要知道所有代码的作用.
由于Rice的定理,这是不可能的,因此没有办法自动检查子类型,因此它会导致错误的数量.
由于这些原因,许多人将子类多态性视为反模式.还有其他形式的多态性不会遇到这些问题,例如"参数多态"(在Java中称为"泛型").
利斯科夫替代原则
子类和子类型之间的比较
参数多态性
反对子分类的论据
赖斯的定理
其他人已经更全面地解决了关于多态性的问题,但我想回答一个特定的部分:
我没有得到它,即使我会直接创建子类的对象.
这实际上是一个大问题,人们为避免这样做而付出了很多努力.如果你打开像Gang of Four这样的东西,有很多模式致力于避免这个问题.
主要方法称为工厂模式.看起来像这样:
AirplaneFactory factory = new AirplaneFactory(); Airplane planeOne = factory.buildAirplane(); Airplane planeTwo = factory.buildJet(); Airplane planeThree = factory.buildRocket();
这通过抽象出对象的实例化为您提供了更大的灵活性.您可能会想到这样的情况:您的公司开始主要构建Jet
s,因此您的工厂有一个buildDefault()
类似于以下的方法:
public Airplane buildDefault() { return new Jet(); }
有一天,你的老板来找你并告诉你业务已经改变了.这几天人们真正想要的是Rocket
s - Jet
s已成为过去.
如果没有AirplaneFactory
,你必须通过你的代码,并可能取代几十个电话来new Jet()
用new Rocket()
.使用Factory模式,您可以进行如下更改:
public Airplane buildDefault() { return new Rocket(); }
所以变化的范围大大减少了.既然你已经编码到接口Airplane
,而不是具体类型Jet
或者Rocket
,这就是你需要做唯一的变化.
据我了解,其优势在于,例如,在飞机格斗游戏中,你必须在每个循环中更新所有飞机的位置,但你有几种不同的飞机.假设你有:
米格-21
韦科10
三菱零
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()
这使您的代码更简单.