【C#基础概念 】多态-----面向对象编程的三大特性 |多态--编程语言和类型论
前言(Preamble)
在理解多态之前先理解设计原则(Solid)中里氏替换原则
里氏替换原则(Liskov Substitution Principle):派生类(子类)对象能够替换其基类(超类)对象被使用。通俗一点的理解就是“子类是父类”,举个例子,“男人是人,人不一定是男人”,当需要一个父类类型的对象的时候可以给一个子类类型的对象;当需要一个子类类型对象的时候给一个父类类型对象是不可以的!
里氏替换原则 满足逻辑 中的充分不必要条件(A是B的充分不必要条件)。以及设计模型的依赖倒置模型。
多态性
定义:在编程语言和类型论中,多态(英語:polymorphism)指为不同数据类型的实体提供统一的接口[1],或使用一个单一的符号来表示多个不同的类型[2]。---维基百科
最常见主要多态类别有:特定多态、参数多态、子类型多态。
在面像对象编程中的多态指 子类型多态,而「参数多态」则一般被称作泛型编程。
编程语言的多态性和面像对象编程的多态有什么区别?
一门编程语言可以写出多种编程范式:面像对象、面像泛型、面像过程、面像函数(表达式)。而面像对象编程的多态性,只是语言多态性一种实现方式之一。
面像对象编程中的多态定义:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。
多态的作用:
多态其实是一种行为的封装,多态实现代码的通用性。继承的作用是实现代码的复用性。
多态的最常见主要类别有:
特定多态(重载)
特定多态(ad hoc polymorphism)是程序设计语言的一种多态,多态函数有多个不同的实现,依赖于其实参而调用相应版本的函数。因此,特设多态仅支持有限数量的不同类型。函数重载乃至运算符重载也是特设多态的一种。是静态多态
特定的多态的实现方式就是:重载
参数多态(泛型)
「参数多态」则一般被称作泛型编程。 在程序设计语言与类型论中是指声明与定义函数、复合类型、变量时不指定其具体的类型,而把这部分类型作为参数使用,使得该定义对各种具体类型都适用。参数化多态使得语言更具表达力,同时保持了完全的静态类型安全。[1] 这被称为泛化函数、泛化数据类型、泛型变量,形成了泛型编程的基础。类类型和方法类型的方向是静态多态 接口类型泛型是动态多态。
特定的多态的实现方式就是:泛型
子类型多态(重写)
也叫包含多态是一种类型多态的形式。一个名字指称很多不同的类的实例,这些类有某个共同的超类[3]。
1、子类型多态遵循里氏替换原则。
2、子类型反映了类型(即面向对象中的接口)之间的关系;而继承反映了一类对象可以从另一类对象创造出来,是语言特性的实现也称作实现继承。
3、在面向对象程序设计中,多态一般仅指这里所说的「子类型多态」。 「参数多态」则一般被称作泛型编程。
4、实现子类型多态是重写(override)。
5、具体表现就是虚函数和抽象函数(接口是抽象函数)。
多态可分为变量多态与函数多态。变量多态是指:基类型的变量(对于C++是引用或指针)可以被赋值基类型对象,也可以被赋值派生类型的对象。函数多态是指,相同的函数调用界面(函数名与实参表),传送给一个对象变量,可以有不同的行为,这视该对象变量所指向的对象类型而定。多态也可定义为“一种将不同的特殊行为和单个泛化记号相关联的能力”,变量多态是函数多态的基础。
具体的子类型多态实现如下:
Animal an =new Birl(); an.Eat(); an.Sleep(); an = new Dog(); an.Eat(); an.Sleep(); public abstract class Animal { public abstract void Eat(); public virtual void Sleep() { Console.WriteLine("在睡觉"); } } public class Birl:Animal { public override void Eat() { Console.WriteLine("鸟在吃饭"); } public override void Sleep() { Console.WriteLine("鸟在树上睡觉"); } } public class Dog : Animal { public override void Eat() { Console.WriteLine("狗在吃饭"); } public override void Sleep() { Console.WriteLine("狗在地上睡觉"); } } /*输出: 鸟在吃饭 鸟在树上睡觉 狗在吃饭 狗在地上睡觉*/
编译时的多态性和运行时的多态性
依据实现时做出的选择,多态可分为:编译时的多态性和运行时的多态性
● 编译时的多态性
编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。C# 提供了两种技术来实现静态多态性。分别为:
- 函数重载
- 运算符重载(运算符的本质就是方法)
您可以在同一个范围内对相同的函数名有多个定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。不能重载只有返回类型不同的函数声明。
● 运行时的多态性
运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中,运行时的多态性通过虚成员实现。C# 提供了两种技术来实现动态多态性分别为:
- 虚方法
- 抽象方法
- 接口
具体的检查的流程如下:
1、当调用一个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调用的函数是否为虚函数;
2、如果不是虚函数,那么它就直接执行该函数。而如果有virtual关键字,也就是一个虚函数,那么这个时候它就不会立刻执行该函数了,而是转去检查对象的实例类。
3、在这个实例类里,他会检查这个实例类的定义中是否有重新实现该虚函数(通过override关键字),如果是有,那么OK,它就不会再找了,而马上执行该实例类中的这个重新实现的函数。而如果没有的话,系统就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重载了该虚函数的父类为止,然后执行该父类里重载后的函数。
class A { public void Greeding() { Console.WriteLine("A:Hello!"); } public virtual void Gree2() { Console.WriteLine("A:Gree2!"); } public virtual void G3() { Console.WriteLine("A:G3!"); } } class B : A { public void Greeding() { Console.WriteLine("B:Hello!"); } public override void Gree2() { Console.WriteLine("B:Gree2!"); } public override void G3() { Console.WriteLine("B:G3!"); } } class C : B { public void GreedC() { base.Greeding(); } public void Gree2() { Console.WriteLine("C:Gree2!"); } public void G3() { base.Gree2(); } } class Test { static void Main() { A aa = new C(); //aa.GreedC(); 报错 C cc = new C(); aa.Greeding();// A:Hello! aa.Gree2(); // B:Gree2! aa.G3(); // B:G3! cc.Gree2(); // C:Gree2! cc.Greeding();// B:Hello! cc.GreedC(); // B:Hello! } }
对于非虚方法来说,调用哪个方法取决于该实例编译时的类型。
但对于虚方法来说,调用哪个方法取决于该实例运行时的类型。