类的多态性,方法重写,方法重载,方法隐藏介绍

我被代码海扁 @codebeatme-s @103466775
阅读 9:13·字数 2767·发布 

前提
阅读本节的前提是对面向对象编程,类,实例等概念有所掌握,你可以查看面向对象编程,类,实例介绍一节来了解他们。

类的多态性

类的多态性是指派生类可以改变调整继承成员的实现,以符合自身的需求,这主要针对实例方法,或本质上是方法的实例成员。多态性使得继承与被继承的对象,在同一功能上的表现可以不同。

变量类型与多态性
在支持面向对象和声明变量类型的语言中,变量的类型与值的数据类型可能不同,通过变量调用的方法,将指向对象对该方法的真实实现,当对象已经对方法做出调整或改变时。

在下面的 C# 代码中,虽然变量kittyanimal的类型都是Animal,且Animal类的Bark方法固定,但实际调用Bark方法却显示了不同的信息,因为变量kitty对应的真实对象的类型是Kitty,对于Bark方法,Kitty类拥有自己的实现。

*.cs
// 创建 Animal 和 Kitty 的实例
Animal animal = new Animal();
Animal kitty = new Kitty();

// 分别调用 Bark 方法,将显示不同信息 Console.WriteLine("animal.Bark"); animal.Bark(); Console.WriteLine("kitty.Bark"); kitty.Bark();
animal.Bark
试着发出声音?
kitty.Bark
试着发出声音?
喵喵喵!

方法重写

方法重写(Method Overriding)是实现多态性的最简单方式,一般的做法是在类中再次定义需要重写的实例方法,此过程可能需要特定的关键字,比如,C# 中的override

什么是虚方法?
虚方法是指允许被派生类重写的方法,通常对应关键字virtual。在一些语言中,虚方法是不存在的,因为默认所有方法均可被重写。

由于虚拟方法允许被重写,所以他不应该具有任何阻止该目标实现的特征,比如,私有的。

在派生类中调用基类的方法
在方法重写的过程中,你可能需要使用基类的方法,如果重写的目的在于补充功能。大部分语言会提供相关的关键字来完成此目标,比如,C# 中的base关键字。

下面展示了之前示例中使用的类AnimalKitty,可以看到Animal类拥有一个虚方法BarkKitty类对该方法进行了重写,并调用了基类的Bark方法。

class_polymorphism.cs
// 基类 Animal
class Animal
{
	// 虚方法 Bark,尝试发出声音
	public virtual void Bark()
	{
		Console.WriteLine("试着发出声音?");
	}
}

// 派生类 Kitty class Kitty : Animal { // Kitty 重写了继承自 Animal 的方法 Bark public override void Bark() { // 调用基类的 Bark 方法 base.Bark();
Console.WriteLine("喵喵喵!"); } }

什么是抽象方法和抽象类?
抽象方法是类中已经声明,但尚未具体实现的方法,他通常需要特定的关键字来标注,比如,C# 中的abstract,并需要派生类来完成实现,一个方法被抽象的原因是其必要性和缺少通用性的逻辑。

由于需要通过派生类完成实现,抽象方法不能具有任何阻止这一目标实现的特征,比如,私有的。

拥有抽象方法的类被称为抽象类,因为没有完全的具体实现,抽象类无法被实例化。需要指出,抽象类包括了,已继承抽象方法但仍未对其实现的派生类。

抽象方法和虚方法之间的区别
抽象方法要求被具体实现,否则相关类无法实例化,虚方法则表示允许被重写,能否实例化与是否被重写无关。

下面,为Animal类增加一个抽象方法RunKitty类需要实现该方法后才能被实例化。

class_polymorphism.cs
// 基类 Animal
abstract class Animal
{
	// …
	// 抽象方法 Run,奔跑起来
	public abstract void Run();
}

// 派生类 Kitty class Kitty : Animal { // … // Kitty 必须实现抽象方法 Run,才能被实例化 public override void Run() { Console.WriteLine("你看,我跑起来了!"); } }
*.cs
// 创建 Kitty 的实例
Kitty kitty = new Kitty();

// 调用 Run 方法 kitty.Run();
你看,我跑起来了!

方法重载

方法重载(Method Overloading)与多态性的关系不大,他允许一个类定义多个名称相同但签名不同的方法,而不是将基类中的方法再次定义。方法重载在一些语言中是不被支持或不可行的,他们不允许一个类拥有名称相同的方法,方法签名仅通过方法名称产生,或者根本没有签名。

重写基类的重载方法需要匹配方法签名
这里需要指出,在重写基类的重载方法时,请注意方法签名是否匹配。

选择方法重载还是可选参数?
当方法内部的逻辑差异不大时,使用可选参数应该是更好,如果语言支持这一功能的话,你将不会看到一排排的方法定义,仅仅是为了多出或缺少的参数。

当不同参数组合带来的逻辑差异较大时,可选择使用方法重载,以使代码更容易维护。

函数
要了解方法签名,你可以查看函数签名一段,好吧,名字不代表一切。

下面,通过重载为Kitty类添加两个同名的Eat方法,他们拥有的参数不同。

class_polymorphism.cs
// 派生类 Kitty
class Kitty : Animal
{
	// …
	// 使用重载定义两个同名的 Eat 方法
	public void Eat(string something)
	{
		Console.WriteLine($"好耶,今天的午餐是:{something}");
	}
	public void Eat()
	{
		Console.WriteLine("今天吃空气?!");
	}
}
*.cs
// 创建 Kitty 的实例
Kitty kitty = new Kitty();

// 调用重载的 Eat 方法 kitty.Eat("小鱼干"); kitty.Eat();
好耶,今天的午餐是:小鱼干
今天吃空气?!

方法隐藏

方法隐藏(Method Hiding/Shadowing)和方法重写是比较容易混淆的,他们均针对基类的方法进行重新定义,但不同的是,通过隐藏定义的方法断绝了自身与基类方法的继承关系,通过重写定义的方法则会保持。方法隐藏和方法重写的选择,将影响多态性的表现效果。

在通过某种类型的变量调用方法时,需要确定实际对象是否存在对该方法的重写,而通过隐藏定义的方法将打断这一过程,前提是这些方法真实的阻断了当前和之后的继承关系,他们不能是私有的,或具有任何导致上述目标无法实现的特征。

在下面的 C# 代码中,变量abcd的类型均为A,但变量b对应的真实对象的类型为B,类B重写了类AShow方法,因此b.Show()指向类BShow方法的实现。变量c对应的真实对象的类型为C,类C隐藏了类BShow方法,c.Show()将不会指向类CShow方法的实现,因为类CShow方法与类BAShow方法不存在继承关系。变量d对应的真实对象的类型为D,类D重写了类CShow方法,d.Show()将不会指向类DCShow方法的实现,因为类DCShow方法与类BAShow方法不存在继承关系。

*.cs
// 所有变量的类型均为 A,但真实的对象类型分别为 A,B,C,D
A a = new A();
A b = new B();
A c = new C();
A d = new D();

// 分别调用他们的 Show 方法 a.Show(); b.Show(); c.Show(); d.Show();
调用了 A 的 Show 方法
调用了 B 的 Show 方法
调用了 B 的 Show 方法
调用了 B 的 Show 方法
class_polymorphism.cs
// 类 A
class A
{
	// 虚方法 Show
	public virtual void Show()
	{
		Console.WriteLine("调用了 A 的 Show 方法");
	}
}

// 类 B class B : A { // 重写类 A 的 Show 方法 public override void Show() { Console.WriteLine("调用了 B 的 Show 方法"); } }
// 类 C class C : B { // 隐藏类 B 的 Show 方法,阻断了继承关系 public new virtual void Show() { Console.WriteLine("调用了 C 的 Show 方法"); } }
// 类 D class D : C { // 重写类 C 的 Show 方法 public override void Show() { Console.WriteLine("调用了 D 的 Show 方法"); } }

源码

class_polymorphism.cs·codebeatme/programming·GitHub