欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第三章 继承与多态

程序员文章站 2022-07-16 09:31:45
...

目录

1 方法的重载、隐藏与重写辨析

1.1 继承关系下的方法重载

1.2 子类隐藏父类的方法

1.3 方法重写与虚方法调用

2 通过实例理解多态

2.1 通过继承实现多态

2.1 利用接口实现多态

3 协变与逆变


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;