一、定义:
多态是面向对象程序设计的又一个特性。在面向过程的程序设计中,主要工作是编写一个个的过程或函数,这些过程和函数不能重名。例如在一个应用中,需要对数值型数据进行排序,还需要对字符型数据进行排序,虽然使用的排序方法相同,但要定义两个不同的过程(过程的名称也不同)来实现。
在面向对象程序设计中,可以利用“重名”来提高程序的抽象度和简洁性。首先我们来理解实际的现象,例如,“启动”是所有交通工具都具有的操作,但是不同的具体交通工具,其“启动”操作的具体实现是不同的,如汽车的启动是“发动机点火——启动引擎”、“启动”轮船时要“起锚”、气球飞艇的“启动”是“充气——解缆”。如果不允许这些功能使用相同的名字,就必须分别定义“汽车启动”、“轮船启动”、“气球飞艇启动”多个方法。这样一来,用户在使用时需要记忆很多名字,继承的优势就荡然无存了。为了解决这个问题,在面向对象的程序设计中引入了多态的机制。
多态是指一个程序中同名的不同方法共存的情况。主要通过子类对父类方法的覆盖来实现多态。这样一来,不同类的对象可以响应同名的方法来完成特定的功能,但其具体的实现方法却可以不同。例如同样的加法,把两个时间加在一起和把两个整数加在一起肯定完全不同。
通过方法覆盖,子类可以重新实现父类的某些方法,使其具有自己的特征。例如对于车类的加速方法,其子类(如赛车)中可能增加了一些新的部件来改善提高加速性能,这时可以在赛车类中覆盖父类的加速方法。覆盖隐藏了父类的方法,使子类拥有自己的具体实现,更进一步表明了与父类相比,子类所具有的特殊性。
多态性使语言具有灵活、抽象、行为共享的优势,很好地解决了应用程序函数同名问题。
二、实现的方法
多态需要通过继承来实现。
三、分类
1、编译多态(重载overload)
eg、 函数重载
重载(overload):在同一个作用域(一般指一个类)的两个或多个方法函数名相同,参数列表不同的方法叫做重载,它们有三个特点(俗称两必须一可以):
- 方法名必须相同
- 参数列表必须不相同
- 返回值类型可以不相同
如:
1 public void Sleep() 2 { 3 Console.WriteLine("Animal睡觉"); 4 } 5 public int Sleep(int time) 6 { 7 Console.WriteLine("Animal{0}点睡觉", time); 8 return time; 9 }
2、运行多态(重写override)
子类中为满足自己的需要来重复定义某个方法的不同实现,需要用override关键字,被重写的方法必须是虚方法,用的是virtual关键字。它的特点是(三个相同):
- 相同的方法名
- 相同的参数列表
- 相同的返回值。
1 如:父类中的定义: 2 public virtual void EatFood() 3 { 4 Console.WriteLine("Animal吃东西"); 5 } 6 7 子类中的定义: 8 9 public override void EatFood() 10 { 11 Console.WriteLine("Cat吃东西"); 12 //base.EatFood(); 13 }
实现方式:
父类引用指向子类
例子:
1 namespace mydemo1 2 { 3 class Ren 4 { 5 public virtual void Speak()// 6 { 7 } 8 } 9 10 class Ghost 11 { 12 public void Eat(Ren e) 13 { 14 e.Speak(); 15 Console.WriteLine("人类真好吃!"); 16 } 17 } 18 19 class China : Ren 20 { 21 public override void Speak() 22 { 23 Console.WriteLine("汉语"); 24 } 25 } 26 27 class Usa : Ren 28 { 29 public override void Speak() 30 { 31 Console.WriteLine("English"); 32 } 33 } 34 35 class Program 36 { 37 public static void Main(string[] args) 38 { 39 Ren a = new China();//父类的引用指向子类的实例 40 Ren b = new Usa(); 41 42 //a.Speak(); 43 //b.Speak(); 44 45 Ghost g = new Ghost(); 46 China c = new China(); 47 Usa u = new Usa(); 48 49 Random r = new Random(); 50 int a = r.Next(0, 3); 51 52 if (a == 1) 53 { 54 g.Eat(c);//改eat()方法传入的参数要求是Ren类的引用,可以向里面传其子类及父类的元素,子类对象代替父类对象 55 } 56 else 57 { 58 g.Eat(u); 59 } 60 61 62 } 63 } 64 }
3、虚方法
:即为基类中定义的允许在派生类中重写的方法,使用virtual关键字定义。
如:
1 public virtual void EatFood() 2 { 3 Console.WriteLine("Animal吃东西"); 4 }
注意虚方法也可以直接调用
Animal a = new Animal(); a.EatFood();
运行结果:
4、抽象方法:在基类中定义的并且必须在派生类中重写的方法,使用abstract关键字定义。如:
public abstract class Biology { public abstract void Live(); } public class Animal : Biology { public override void Live() { Console.WriteLine("Animal重写的抽象方法"); //throw new NotImplementedException(); } }
注意:抽象方法只能在抽象类中定义,如果不在抽象类中定义,则会报出如下错误:
5、里氏代换原则和抽象依赖原则
里氏代换原则
如果某个方法接收的是父类引用,可以向里面传父类或其子类的元素,子类对象替代父类对象
例子:怪兽吃人
1 class Guaishou 2 { 3 public void Eat(Ren r) 4 { 5 r.Jiao(); 6 Console.WriteLine("人类真好吃"); 7 } 8 } 9 10 class Ren 11 { 12 public virtual void Speak() 13 { 14 Console.WriteLine("说话"); 15 } 16 17 public virtual void Jiao() 18 { 19 Console.WriteLine("55555555555555555"); 20 } 21 22 } 23 24 class American:Ren 25 { 26 public override void Speak() 27 { 28 Console.WriteLine("Hello"); 29 } 30 31 public override void Jiao() 32 { 33 Console.WriteLine("SOS"); 34 } 35 } 36 37 class Chinese:Ren 38 { 39 public override void Speak() 40 { 41 Console.WriteLine("你好"); 42 } 43 public override void Jiao() 44 { 45 Console.WriteLine("救命"); 46 } 47 48 } 49 50 class Program 51 { 52 static void Main(string[] args) 53 { 54 //Ren a = new Chinese(); 55 //a.Speak(); 56 //a = new American(); 57 //a.Speak(); 58 59 Guaishou g = new Guaishou(); 60 61 //Ren ren; 62 63 Random r = new Random(); 64 int a = r.Next(0,3); 65 66 if(a==1) 67 { 68 American ren= new American(); 69 g.Eat(ren); 70 } 71 else 72 { 73 Chinese ren = new Chinese(); 74 g.Eat(ren); 75 } 76 77 78 } 79 } 80 }
抽象依赖原则
用父类的引用来指向子类的实例
例子:运行多态的例子
6、隐藏方法:在派生类中定义的和基类中的某个方法同名的方法,使用new关键字定义。
如在基类Animal中有一方法Sleep():
public void Sleep() { Console.WriteLine("Animal Sleep"); }
则在派生类Cat中定义隐藏方法的代码为:
new public void Sleep() { Console.WriteLine("Cat Sleep"); }
或者为:
public new void Sleep() { Console.WriteLine("Cat Sleep"); }
注意:(1)隐藏方法不但可以隐藏基类中的虚方法,而且也可以隐藏基类中的非虚方法。
(2)隐藏方法中父类的实例调用父类的方法,子类的实例调用子类的方法。
(3)和上一条对比:重写方法中子类的变量调用子类重写的方法,父类的变量要看这个父类引用的是子类的实例还是本身的实例,如果引用的是父类的实例那么调用基类的方法,如果引用的是派生类的实例则调用派生类的方法。
7、例题
1 public abstract class Biology 2 { 3 public abstract void Live(); 4 } 5 public class Animal : Biology 6 { 7 public override void Live() 8 { 9 Console.WriteLine("Animal重写的Live"); 10 //throw new NotImplementedException(); 11 } 12 public void Sleep() 13 { 14 Console.WriteLine("Animal Sleep"); 15 } 16 public int Sleep(int time) 17 { 18 Console.WriteLine("Animal在{0}点Sleep", time); 19 return time; 20 } 21 public virtual void EatFood() 22 { 23 Console.WriteLine("Animal EatFood"); 24 } 25 } 26 public class Cat : Animal 27 { 28 public override void EatFood() 29 { 30 Console.WriteLine("Cat EatFood"); 31 //base.EatFood(); 32 } 33 new public void Sleep() 34 { 35 Console.WriteLine("Cat Sleep"); 36 } 37 //public new void Sleep() 38 //{ 39 // Console.WriteLine("Cat Sleep"); 40 //} 41 } 42 public class Dog : Animal 43 { 44 public override void EatFood() 45 { 46 Console.WriteLine("Dog EatFood"); 47 //base.EatFood(); 48 } 49 }
需要执行的代码:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //Animal的实例 6 Animal a = new Animal(); 7 //Animal的实例,引用派生类Cat对象 8 Animal ac = new Cat(); 9 //Animal的实例,引用派生类Dog对象 10 Animal ad = new Dog(); 11 //Cat的实例 12 Cat c = new Cat(); 13 //Dog的实例 14 Dog d = new Dog(); 15 //重载 16 a.Sleep(); 17 a.Sleep(23); 18 //重写和虚方法 19 a.EatFood(); 20 ac.EatFood(); 21 ad.EatFood(); 22 //抽象方法 23 a.Live(); 24 //隐藏方法 25 a.Sleep(); 26 ac.Sleep(); 27 c.Sleep(); 28 Console.ReadKey(); 29 } 30 }