继承和多态多态的概念
灵活的程序专注于多态而不是继承 。 一些语言专注于静态类型检查(C ++,Java,C#),它们将概念联系在一起并减少了多态机会 。 分离概念的语言可以使您专注于多态并创建更强大的代码。 JavaScript,Python,Ruby和VB.NET没有类型变量并将类型检查推迟到运行时。 静态类型检查的值是否值得在运行时放弃纯多态性的功能?
继承和多态性是独立但相关的实体–可能有一个却没有另一个。 如果我们使用要求变量具有特定类型的语言(C ++,C#,Java)
那么我们可能会相信这些概念是相互联系的。 如果你只用Ruby在Python中使用不需要变量与特定类型,即VAR在JavaScript中声明的语言, 闪避 , 闪避 , 朦胧中VB.NET,那么你可能不知道,我嘎嘎叫一下! Ĵ
我相信纯多态性的好处胜过静态类型检查的价值。 现在我们有了快速的处理器,复杂的调试器和运行时异常,构造类型在编译时的检查值很小。 在多态性方面有些挣扎,所以让我们定义一下:
多态 是一种在不知道其类型的情况下向对象发送消息的能力。
多态性是我们可以彼此驾驶汽车以及可以使用不同的电灯开关的原因。 汽车是多态的,因为您可以在不知道世界卫生组织制造汽车的情况下将普遍理解的消息发送到任何汽车( 开始 (), 加速 (), turnLeft (), turnRight ()等)。 电灯开关是多态的,因为您可以将消息turnOn ()和turnOff ()发送到任何电灯开关,而无需知道是谁制造的。
从本质上讲,多态性是使我们的经济运转的原因。 它使我们能够构建功能完全相同的产品,这些产品可能具有截然不同的实现方式。 这是造成产品(例如烤面包机,搅拌器等)价格和质量差异的基础。
通过继承实现多态
上面的UML图显示了如何在C ++,Java和C#等语言中声明多态。 方法(aka操作) start ()被声明为抽象的 (在UML中),这将方法的实现推迟到目标语言中的子类。 start ()的方法在Car类中声明,并且仅指定方法签名,而不指定实现(技术上的多态性要求在Car类中不存在方法start ()的代码)。
然后在VolkswagenBeetle和SportsCar子类中分别实现方法start ()的代码。 多态性意味着start ()是使用子类中的不同属性实现的,否则start ()方法可以简单地在超类Car中实现 。 即使我们大多数人不再使用C ++编写代码,了解继承和多态性之间的牢固联系为何会破坏灵活性还是很有启发性的。
// C++ polymorphism through inheritance
class Car {
// declare signature as pure virtual function
public virtual boolean start() = 0;
}
class VolkswagenBeetle : Car {
public boolean start() {
// implementation code
}
}
class SportsCar : Car {
public boolean start() {
// implementation code
}
}
// Invocation of polymorphism
Car cars[] = { new VolkswagenBeetle(), new SportsCar() };
for( I = 0; I < 2; i++)
Cars[i].start();
cars数组是Car类型的,只能容纳从Car派生的对象( VolkswagenBeetle和SportsCar ),并且多态性按预期工作。 但是,假设我的C ++程序中还有以下附加类:
// C++ lack of polymorphism with no inheritance
class Jalopy {
public boolean start() {
…
}
}
// Jalopy does not inherit from Car, the following is illegal
Car cars[] = { new VolkswagenBeetle(),new Jalopy() };
for( I = 0; I < 2; i++)
Cars[i].start();
在编译时,这会产生错误,因为Jalopy类型不是从Car派生的。 即使它们都使用相同的签名实现start ()方法,编译器也会停止我,因为存在静态类型错误。 在编译时进行强类型检查意味着所有多态性都必须通过继承来实现。 这会导致具有深层次继承层次结构和多重继承的问题,其中存在各种具有意外副作用的问题。 甚至中等复杂的程序也变得很难用C ++理解和维护。
历史记录:直到1990年代中期,C ++才占主导地位,仅仅是因为它是一种未经解释的面向对象解决方案。 这意味着在当时速度较慢的CPU上,它具有不错的性能。 我们之所以使用C ++,是因为我们无法获得当时任何解释型的面向对象的语言(即Smalltalk)的可比性能。
削弱链接
继承和多态性之间紧密联系的负面影响导致Java和C#都引入了接口概念,以撬开继承和多态性的思想,但在编译时保持强类型检查。 首先,可以使用继承来实现上述C ++示例,如下面的C#所示:
// C# polymorphism using inheritance
class Car {
public virtual boolean start(); // declare signature
}
class VolkswagenBeetle : Car {
public override boolean start() {
// implementation code
}
}
class SportsCar : Car {
public override boolean start() {
// implementation code
}
}
// Invocation of polymorphism
Car cars[] = { new VolkswagenBeetle(), new SportsCar() };
for( I = 0; I < 2; i++)
Cars[i].start();
此外,通过使用接口概念,我们可以使用Java编写类,如下所示:
// Java polymorphism using interface
interface Car {
public boolean start();
}
class VolkswagenBeetle implements Car {
public boolean start() {
// implementation code
}
}
class SportsCar implements Car {
public boolean start() {
// implementation code
}
}
通过使用接口,VolkswagenBeetle和SportsCar的实现可以完全独立,只要它们继续满足 Car 接口即可 。 通过这种方式,我们现在可以通过以下简单操作将Jalopy类与其他两个类进行多态处理:
class Jalopy implements Car {
…
}
没有继承的多态
在某些语言中,您具有多态而不使用继承 。 例如JavaScript,Python,Ruby,VB.NET和Small Talk。 在每种语言中,都可以编写car.start ()而不了解对象car及其方法。
# Python polymorphism
class VolkswagenBeetle(Car):
def start(): # Code to start Volkswagen
class SportsCar(Car):
def start(): # Code to start SportsCar
# Invocation of polymorphism
cars = [ VolkswagenBeetle(), SportsCar() ]
for car in cars:
car.start()
获得纯多态性的能力,从这些语言茎只有之前在JavaScript运行即VAR单一变量类型,DEF在Python,DEF在Ruby中,在VB.NET 暗淡 。 仅使用一种变量类型,运行时之前就不会发生类型错误。
历史记录:仅在引入Java和C#的时间范围内,CPU能力才足以使解释型语言在运行时提供足够的性能。 从具有多态性和继承紧密耦合到更松散耦合的过渡取决于运行时解释器能够以良好的性能执行实际应用。
没有免费的午餐
当类型检查推迟到运行时时,当您对未实现该方法的对象进行方法调用时 (例如,将start ()发送给没有start()方法的对象),可能会导致奇怪的行为。 如果将类型检查推迟到运行时,则如果您不小心将start ()方法发送给对象,则您希望对象响应“ 我不知道如何开始 () ”。
某些纯多态语言通常具有检测丢失方法的方法:
- 在Visual Basic中,您可以获取NotImplementedException
- 在Ruby中,您要么实现方法_ missing()方法,要么捕获NoMethodError异常
- 在Smalltalk中,您将获得#didNotUnderstand异常
某些语言没有例外,但有些笨拙的解决方法:
- 在Python中,必须使用getattr ()调用来查看名称是否存在属性,然后使用callable()来确定是否可以调用它。 对于上面的汽车示例,它看起来像:
startCar = getattr(obj, "start", None)
if callable(startCar):
startCar ()
- JavaScript( ECMAScript )仅会因Firefox / Spidermonkey中缺少的方法而引发异常。
即使您有一个定义良好的异常机制(即尝试捕获),将类型检查推迟到运行时,也很难证明您的程序可以正常工作。 在开发时未类型化的变量允许开发人员创建异类对象(即集合,包,向量,地图,数组)的集合。 当您遍历这些异构集合时,总是有可能在未实现的对象上调用方法。 即使您在这种情况下遇到异常,也要花很长时间才能发现细微的问题。
结论
仅当您的语言需要静态类型检查( C ++ , Java , C#等)时,才将多态和继承的概念链接在一起。 任何仅具有用于变量声明的通用类型的语言,都可以将多态性和继承( JavaScript , Python , Ruby , VB.NET )完全分离,无论它们是编译为字节码还是直接解释。 由于性能问题,原始的编译语言( C ++等)执行静态类型检查。
在编译时执行类型检查会在继承和多态之间建立牢固的联系。 需要深层的类继承结构和多重继承会导致运行时副作用和难以理解的代码。
诸如C#和Java之类的语言使用接口的概念来保留编译时的类型检查,以削弱继承和多态性之间的联系。 另外,这些语言编译为字节代码 ,该字节代码在运行时进行解释,以平衡静态类型检查和运行时性能。
诸如Ruby , Python , JavaScript , Visual Basic和Smalltalk之类的语言利用功能强大的CPU来使用解释器来推迟类型检查的运行时间(无论源代码是编译为字节码还是纯编译)。 通过推迟类型检查,我们打破了继承和多态之间的联系,但是,这种能力带来的困难是难以证明不会出现细微的运行时问题。
纯多态的一个警告是,我们可能会开发出难以跟踪和修复的细微错误 。 仅当您使用的语言在未实现方法时可以可靠地引发异常时,才值得寻求纯多态。
有效的程序员正在寻求多态而不是继承 。 纯多态的好处胜过编译时类型检查提供的任何好处,尤其是当我们可以使用非常复杂的调试器并支持运行时异常处理时。 总的来说,我相信纯多态性的好处胜过静态类型检查的价值。
继承和多态多态的概念