第三章 继承与多态
目录
1 方法的重载、隐藏与重写辨析
由于子类对象同时包含了父类和子类定义的所有公共方法,因此,子类方法与父类方法之间的关系可以概括为以下三种:
扩充:父类中没有与子类同名的方法
重载:子类有父类的同名方法,但参数类型或数目不一样。
完全相同:子类方法与父类方法从方法名称到参数列表都完全一样。
1.1 继承关系下的方法重载
构成 “重载(Overload)” 的方法具有以下特点:
(1)方法名相同。
(2)方法参数列表不同。
构成重载的方法主要根据参数列表来决定调用哪一个。
class Parent
{
public void OverloadF(){ }
}
class Child : Parent
{
public void OverloadF(int i) { }
}
Child obj = new Child();
obj.OverloadF(); // 调用父类的重载方法
obj.OverloadF(100); // 调用子类的重载方法
1.2 子类隐藏父类的方法
当子类与父类拥有完全一样的方法时,称子类 “隐藏” 了父类的同名方法。
class Parent
{
public void HideF()
{
Console.WriteLine("Parent.HideF()");
}
}
class Child : Parent
{
public void HideF()
{
Console.WriteLine("Child.HideF()");
}
}
当子类和父类具有完全相同的方法 HideF,则其输出:
Child c = new Child();
c.HideF(); // 输出:Child.HideF()
Parent p = new Parent();
p.HideF(); // 输出:Parent.HideF()
Parent p1 = new Child();
p1.HideF(); // 输出:Parent.HideF()
当分别位于父类和子类的两个方法完全一样时,调用哪个方法由对象变量的类型决定。
回过头来再看看Parent 和 Child类,Visual Studio 在编译这两个类时会发出一个警告:
修改 Child 类的代码,使其符合C#的语法规范:
class Child : Parent
{
public new void HideF()
{
Console.WriteLine("Child.HideF()");
}
}
“new” 关键子明确告诉 C# 编译器:子类会隐藏父类的同名方法,并提供自己的新版本。
由于子类隐藏了父类的同名方法,所以当我们需要在子类方法中调用父类被隐藏的同名方法,需要用 base 关键字,示例代码如下:
class Child : Parent
{
public new void HideF()
{
Console.WriteLine("Child.HideF()");
base.HideF(); // 调用父类被隐藏的方法
}
}
1.3 方法重写与虚方法调用
父类同名方法前加关键字 virtual,表明这是一个虚方法,子类可以重写此方法。与此同时,需要在子类同名方法前加关键字 override,表明对父类同名方法进行了重写。
class Parent
{
public virtual void OverrideF()
{
Console.WriteLine("Parent.HideF()");
}
}
class Child : Parent
{
public override void OverrideF()
{
Console.WriteLine("Child.HideF()");
}
}
Parent p = new Child();
p.OverrideF(); // 输出:Child.OverrideF()
面向对象语言拥有的“虚方法调用”特性,使我们可以只用同样的一个语句,在运行时根据对象类型而执行不同的操作,让代码具有“运行时功能可变”的特性。
2 通过实例理解多态
多态编程的基本原理:
使用基类或接口变量编程。
在多态编程中,基类一般都是抽象基类,其中拥有一个或多个抽象方法,各个子类可以根据需要重写这些方法。
或者使用接口,为每个接口定义一个或多个方法,由实现接口的类根据实际需要提供这些方法的具体实现。
因此,多态的实现分为两大基本类别:继承多态和接口多态(类继承 和 接口继承)
2.1 通过继承实现多态
class Feeder
{
public void FeedAnimal(Animal animal)
{
animal.Eat();
}
public void AnimalRun(Animal animal)
{
animal.Run();
}
}
Feeder Kevin = new Feeder();
Kevin.FeedAnimal(new Monkey()); // 喂猴子
Kevin.FeedAnimal(new Pigeon()); // 喂鸽子
Kevin.FeedAnimal(new Lion()); // 喂狮子
Kevin.AnimalRun(new Monkey()); // Monkey Run
Kevin.AnimalRun(new Pigeon()); // Run
Kevin.AnimalRun(new Lion()); // Run
// 定义一个Aniaml抽象基类
abstract class Animal
{
abstract public void Eat(); // 抽象方法在基类中不可以有具体的实现
public virtual void Run() // 虚方法在基类中必须有具体的实现
{
Console.WriteLine("Run");
}
}
// 继承自抽象类的非抽象类,必须实现抽象类的所有抽象方法
class Monkey : Animal
{
// 抽象方法要求必须在派生类中对其进行重写
public override void Eat()
{
Console.WriteLine("喂猴子");
}
// 虚方法可以在派生类中对其进行重写,也可以不对其进行重写
public override void Run()
{
Console.WriteLine("Monkey Run");
}
}
class Pigeon : Animal
{
public override void Eat()
{
Console.WriteLine("喂鸽子");
}
}
class Lion : Animal
{
public override void Eat()
{
Console.WriteLine("喂狮子");
}
}
一种类型只用于其它类型派生,从来不需要创建它的某个具体对象实例,这样的类高度抽象化,我们称这种类为 “抽象类”,抽象类不负责创建具体的对象实例,它包含了派生类型的共同成分。
总结:
抽象方法与虚方法的区别:
- 抽象方法必须定义在一个抽象类中,虚方法没有特殊要求
- 抽象方法在基类中不可以有具体的实现,而虚方法在基类中必须有具体的实现
- 抽象方法要求必须在派生类中对其进行重写;而虚方法可以在派生类中对其进行重写,也可以不对其进行重写
2.1 利用接口实现多态
接口实现。 接口定义了一组方法,所有实现了该接口的类型必须实现接口中
所有的方法:
interface IWalkable
{
void Walk(); // 接口不能包含字段和方法实现(可以定义属性)
}
class People : IWalkable // 实现接口
{
public void Walk()
{
Console.WriteLine("walk quickly");
}
}
class Dog : IWalkable
{
public void Walk()
{
Console.WriteLine("walk slowly");
}
}
IWalkable obj = null;
obj = new People();
obj.Walk();
obj = new Dog();
obj.Walk();
3 协变与逆变
面向对象的程序中父类变量可以引用子类对象。
.NET基类库中的泛型委托 Action<T> 的定义:
public delegate void Action<in T>(T obj);
.NET基类库中的泛型委托 Func<T , TResult> 的定义:
public delegate TResult Func<in T, out TResult>(T arg);
总结如下:
(1)类型参数前有 “out” 的,它可以接收子类型,叫 “协变(Covariance)”
public interface IEnumerable<out T> : IEnumerable
IEnumerable<string> strings = null;
IEnumerable<object> objects = strings;
(2)类型参数前有 “in” 的,它可以接收父类型,叫 “逆变(Contravariance)”
public interface IComparer<in T>
IComparer<object> objComp = null;
IComparer<string> stringComp = objComp;
上一篇: C++中的类---封装与访问控制
下一篇: PTA:7-10 方阵转置