C#面向对象——多态
什么是多态?
(1)允许不同的类实现相同的接口或者父类,但具有不同的实现方式。
(2)相同的消息可以由不同的对象接收并以不同的方式响应的能力。
(3)多态使得程序可以根据实际情况调用相应的方法,无需明确的知道对象的具体类型。
多态的两种形式:
- 编译时多态(静态多态): 编译时多态是指在编译时就确定了调用的方法。这种多态性主要通过方法的重载(Overloading)和重写(Overriding)来实现。
- 方法重载(Overloading): 同一个类中可以定义多个方法具有相同的名字,但参数类型、参数个数或参数顺序不同。编译器根据方法调用时传递的参数类型和个数来确定具体调用哪个方法。
- 方法重写(Overriding): 方法重写是子类重写父类中已有的方法,子类可以根据自己的需求提供特定的实现。重写的方法具有相同的名称、参数列表和返回类型,并且访问修饰符不能比父类的方法更严格。在运行时,根据对象的实际类型来确定调用的是子类的方法还是父类的方法。
- 运行时多态(动态多态): 运行时多态是指在程序运行时根据对象的实际类型确定调用的方法。这种多态性主要通过继承和接口实现。
- 继承: 子类可以继承父类的方法,并且可以通过重写父类已有的方法来实现多态性。在运行时,根据对象的实际类型来确定调用的是子类的方法还是父类的方法。
- 接口实现: 类可以实现一个或多个接口,接口定义了一组抽象方法。不同的类实现了相同的接口,但具有不同的实现方式。在运行时,可以根据对象实现的接口来确定调用的方法。
多态的优势:
- 灵活性: 多态性使得程序可以根据对象的实际类型来调用相应的方法,而不需要显式地区分对象的类型,提高了代码的灵活性和可扩展性。
- 可扩展性: 多态性允许新的子类可以覆盖(重写)父类的方法,从而实现新的行为,增强了代码的可扩展性。
- 代码重用性: 多态性允许不同的类实现相同的接口或继承相同的父类,从而实现了代码的重用。
- 简化接口: 多态性使得代码可以针对抽象接口编程,而不需要关心具体的实现类,降低了代码的耦合度,提高了代码的可维护性。
示例代码:
方法重载(Overloading):
public class MathUtility
{
// 方法重载示例
public int Add(int a, int b)
{
return a + b;
}
//参数类型不同
public double Add(double a, double b)
{
return a + b;
}
}
在上面的示例中,MathUtility 类中定义了两个名为 Add 的方法,一个接受两个整数参数,另一个接受两个双精度浮点数参数。编译器根据方法调用时传递的参数类型来确定调用哪个方法。
继承和方法重写(Overriding):
using System;
namespace OverridingTest
{
//动物类
public class Animal
{
//发出声音的方法
public virtual void MakeSound()
{
Console.WriteLine("动物发出了叫声\n");
}
}
//Dog继承了动物类
public class Dog : Animal
{
//重写父类已有的方法
public override void MakeSound()
{
Console.WriteLine("小狗小狗汪汪汪\n");
}
}
//Cat继承了动物类
public class Cat : Animal
{
//重写父类已有的方法
public override void MakeSound()
{
Console.WriteLine("小猫小猫喵喵喵\n");
}
}
internal class Program
{
private static void Main(string[] args)
{
//定义父类对象并调用方法
Animal animal = new Animal();
animal.MakeSound();
//定义dog对象并调用方法
Dog dog = new Dog();
dog.MakeSound();
//定义cat对象并调用方法
Cat cat = new Cat();
cat.MakeSound();
}
}
}
在上面的示例中,Animal 类定义了一个名为 MakeSound 的虚方法,而 Dog 类和 Cat 类分别重写了 MakeSound 方法并提供了自己的实现。当通过 Animal 类的引用调用 MakeSound 方法时,实际上会根据对象的实际类型(Dog 或 Cat)调用相应的重写方法。
注意事项:
- 正确的设计继承关系: 多态性通常通过继承和接口实现,因此需要正确地设计类之间的继承关系和接口的实现,避免过度继承或滥用继承导致继承层次结构过于复杂。
- 保持一致性: 子类覆盖父类的方法时,应该保持方法签名的一致性,即方法名、参数类型和返回类型都应该相同或兼容,否则可能导致编译错误或运行时异常。
- 运行时性能影响: 在运行时确定调用的方法可能会影响性能,因为需要进行方法查找和动态绑定。在性能敏感的场景下,可以考虑采用编译时多态来提高性能。
- 了解动态绑定: 在多态性中,方法调用是动态绑定的,即在运行时确定调用的方法。因此,需要了解动态绑定的原理和机制,以避免出现意外的行为。
- 避免歧义: 当一个对象实现了多个接口或继承了多个类时,可能会出现方法调用的歧义。在这种情况下,应该明确指定调用的方法或进行适当的类型转换,以消除歧义。