什么是多态性,它有什么作用,以及它如何被使用?
什么是多态性,它有什么作用,以及它如何被使用?
多态字面上意思是多种形式。不同类的对象有相同名称的方法,但工作流程不同。 一个简单的例子是:
考虑一个人X。
他只是一个人,但可以扮演多种角色。 你可能会问如何实现:
他是他母亲的儿子。 他是他朋友的朋友。 他是他姐姐的兄弟。
面向对象编程中的多态性(Polymorphism),意味着一个类可以有不同的类型,继承是实现多态性的一种方式。
例如,Shape 是一个接口,它有 Square、Circle、Diamond 三个子类型。现在你有一个 Square 对象,你可以自动将 Square 向上转型为 Shape,因为 Square 是 Shape。但是,当你尝试将 Shape 向下转型为 Square 时,必须进行显式的类型转换,因为你不能说 Shape 就是 Square,它也可能是 Circle。
所以你需要手动使用代码进行强制类型转换,例如:Square s = (Square)shape
,如果 shape 是 Circle,你将得到 java.lang.ClassCastException
异常,因为 Circle 不是 Square。
什么是多态性?
多态性是指能够:
通过只知道其一般类型而调用特定类型的方法来调用专门类型实例上的操作:
这是动态多态性。
定义具有相同名称但具有不同参数的多个方法:
这是静态多态性。
第一个是历史定义,也是最重要的定义。
多态性有什么用处?
它允许创建类层次结构的强类型一致性,并进行一些“神奇”的事情,比如管理不同类型对象的列表而不知道它们的类型,只知道它们的父类型之一,以及数据绑定。
示例
这里有一些形状,如点、线、矩形和圆,具有 Draw() 操作,可以接受没有参数或者一个参数来设置超时时间来擦除它。
public class Shape
{
public virtual void Draw()
{
DoNothing();
}
public virtual void Draw(int timeout)
{
DoNothing();
}
}
public class Point : Shape
{
int X, Y;
public override void Draw()
{
DrawThePoint();
}
}
public class Line : Point
{
int Xend, Yend;
public override Draw()
{
DrawTheLine();
}
}
public class Rectangle : Line
{
public override Draw()
{
DrawTheRectangle();
}
}
var shapes = new List<Shape> { new Point(0,0), new Line(0,0,10,10), new rectangle(50,50,100,100) };
foreach ( var shape in shapes )
shape.Draw();
这里应该将Shape类和Shape.Draw()方法标记为抽象。
它们的作用不是为了让人理解。
说明
在没有多态性的情况下,使用抽象-虚拟-重写,当解析形状时,只调用Spahe.Draw()方法,因为CLR不知道调用哪个方法。所以它调用我们所操作的类型的方法,这里由于列表声明,类型是Shape。所以代码根本不做任何事情。
有了多态性,CLR能够使用称为虚表的东西推断我们所操作的对象的真实类型。所以它调用“好”的方法,在这里如果Shape是Point,则调用Point.Draw()而不是Shape.Draw()。所以代码绘制形状。
更多阅读
多态性是指在一个给定的类中使用对象的能力,其中组成该对象的所有组件都被该类的子类继承。这意味着,一旦声明了该对象,所有位于其下方的子类(以及它们的子类,依此类推,直到达到最远/最低的子类)都会继承该对象及其组件。
请记得每个类必须保存在单独的文件中。
以下代码举例说明了多态性:
超类:
public class Parent {
//Define things that all classes share
String maidenName;
String familyTree;
//Give the top class a default method
public void speak(){
System.out.println("We are all Parents");
}
}
public class Father extends Parent{
//Can use maidenName and familyTree here
String name="Joe";
String called="dad";
//Give the top class a default method
public void speak(){
System.out.println("I am "+name+", the father.");
}
}
孩子,另一个子类:
public class Child extends Father {
//Can use maidenName, familyTree, called and name here
//Give the top class a default method
public void speak(){
System.out.println("Hi "+called+". What are we going to do today?");
}
}
public class Parenting{
public static void main(String[] args) {
Parent parents = new Parent();
Parent parent = new Father();
Parent child = new Child();
parents.speak();
parent.speak();
child.speak();
}
}
多态允许相同的例程(函数、方法)对不同的类型进行操作。
由于许多现有答案将子类型与多态混淆在一起,因此这里列举了三种实现多态的方式(包括子类型)。
另请参见:
http://wiki.c2.com/?CategoryPolymorphism
https://en.wikipedia.org/wiki/Polymorphism_(computer_science)
/// <summary>
/// Base class for car objects
/// </summary>
public abstract class Car
{
public virtual void StartEngine()
{
Console.WriteLine(value: "Car engine has been started.");
}
public virtual void StopEngine()
{
Console.WriteLine(value: "Car engine has been stopped.");
}
}
一旦我们定义了基类,让我们定义派生类。
/// <summary>
/// Example of polymorphism in C# using abstract classes on Mercedes Benz cars
/// </summary>
public class MercedesBenzCar : Car
{
public override void StartEngine()
{
Console.WriteLine(value: "Turning on the ignition and starting the Mercedes-Benz S Class...");
}
public override void StopEngine()
{
Console.WriteLine(value: "Turning off the ignition and stopping the Mercedes-Benz S Class...");
}
}
/// <summary>
/// Example of polymorphism in C# using abstract classes on Tesla Electric Cars
/// </summary>
public sealed class TeslaModelSCar : Car
{
public override void StartEngine()
{
Console.WriteLine(value: "The electric motor in the Tesla Model S car was activated...");
}
public override void StopEngine()
{
Console.WriteLine(value: "The electric motor in the Tesla Model S car was deactivated...");
}
}
那么这有什么意义呢?在这个例子中,基类 Car
的 StartEngine()
和 StopEngine()
方法展示了如何启动和停止汽车。MercedesBenzCar
和 TeslaModelSCar
类重写这些方法,以提供各自独特的电动和燃油汽车行为实现。因此,直接在派生类中定义行为比在基类中使用多态性来描述更有意义。
正如您所看到的,如果每个派生类中的 StartEngine()
或 StopEngine()
方法表现非常不同,那么这种设计可能不是一个好主意。这是因为在基类中定义有意义的 StartEngine()
或 StopEngine()
方法的实现需要大量工作。在这种情况下,直接在派生类中定义 StartEngine()
或 StopEngine()
方法而不是使用多态性可能更好。
我们如何解决这个问题呢?
让我们再次看一下这个例子,看看当派生类的行为非常不同时,如何在C#中使用继承和多态。
关于重构,我想建议引入接口或合同作为解决此问题的潜在方法,并为我们提供更好的设计。在谈论接口之前,让我们先了解它们是什么。接口可以被视为对描述其属性和方法的类或结构的提案。
C#的新版本可能会有默认实现,但让我们不要把事情弄得更难。如果您想了解更多信息,请查看link。简而言之,它们说明成员签名应该是什么样子的,但不说明实现应该是什么样子的。
我想提议 ICar
接口定义两个方法,StartEngine()
和 StopEngine()
。 MercedesBenzCar
和 TeslaModelSCar
类应该实现 ICar
接口,这意味着我们可以摆脱抽象类并将其转换为接口。 因此,Car
应该变成 ICar
。
为什么?这比继承更灵活和可持续,因为您可以从类中添加或删除 ICar
接口,而不影响类继承层次结构。 现在让我们重构上面的解决方案以实践它。
/// <summary>
/// Base interface for all car types.
/// </summary>
public interface ICar
{
/// <summary>
/// Use this method to turn on the car engine.
/// </summary>
void StartEngine();
/// <summary>
/// Use this method to turn off the car engine.
/// </summary>
void StopEngine();
}
ICar
被实现,现在是时候重构具体类了。/// <summary>
/// Example of using the interface ICar for the class TeslaModelSCar.
/// </summary>
public sealed class TeslaModelSCar : ICar
{
public void StartEngine()
{
Console.WriteLine(value: "The electric motor in the Tesla Model S car was activated...");
}
public void StopEngine()
{
Console.WriteLine(value: "The electric motor in the Tesla Model S car was deactivated...");
}
}
/// <summary>
/// Example of using the interface ICar for the class MercedesBenzCar.
/// </summary>
public class MercedesBenzCar : ICar
{
public void StartEngine()
{
Console.WriteLine(value: "Turning on the ignition and starting the Mercedes-Benz S Class...");
}
public void StopEngine()
{
Console.WriteLine(value: "Turning off the ignition and stopping the Mercedes-Benz S Class...");
}
}
重构过程现在已经完成。现在是时候看看如何利用它了。
ICar teslaModelS = new TeslaModelSCar();
ICar mercedesBenz = new MercedesBenzCar();
teslaModelS.StartEngine();
mercedesBenz.StartEngine();
Console.WriteLine(value: "Press any key to stop engines...");
Console.ReadLine();
teslaModelS.StopEngine();
mercedesBenz.StopEngine();
await Task.Delay(delay: TimeSpan.FromSeconds(value: 3)); // Wait for 3 seconds in order to see the output
想象一下,我们的车库里有一堆汽车,并且有另一种方法可以按顺序启动它们的引擎。
var carsInGarage = new ICar[2] { new TeslaModelSCar(), new MercedesBenzCar() };
foreach (var car in carsInGarage)
{
car.StartEngine();
}
Console.WriteLine(value: "Press any key to stop engines...");
Console.ReadLine();
foreach (var car in carsInGarage)
{
car.StopEngine();
}
await Task.Delay(delay: TimeSpan.FromSeconds(value: 3)); // Wait for 3 seconds in order to see the output
再说一遍,多态是一种强大的面向对象编程方法,可以让您定义由多个类共享的行为。但是,像任何其他编程方法一样,有时候多态可能不是最佳选择。我相信在以下情况下应该考虑使用接口:
在决定是否在代码中使用多态时,我认为有必要牢记这些事情。
干杯!
多态性使您能够创建一个模块调用另一个模块,但编译时依赖点针对控制流而不是与控制流相同。
通过使用多态性,高级模块不依赖于低级模块。两者都依赖于抽象。这有助于我们应用依赖反转原则(https://en.wikipedia.org/wiki/Dependency_inversion_principle)。
这就是我找到上述定义的地方。在视频的约50分钟处,讲师解释了上述内容。 https://www.youtube.com/watch?v=TMuno5RZNeE