C#面向对象之多态
一、多态
多态是从继承中引出来的概念,即不同的派生类对基类的相同方法表现出不同的行为。如下面例子中动物的游泳方法:
1 /// <summary> 2 /// 动物 3 /// </summary> 4 public class animal 5 { 6 public string swimming() 7 { 8 return "不会"; 9 } 10 } 11 /// <summary> 12 /// 鸡 13 /// </summary> 14 public class chicken : animal 15 { 16 17 } 18 /// <summary> 19 /// 狗 20 /// </summary> 21 public class dog : animal 22 { 23 } 24 /// <summary> 25 /// 蛙 26 /// </summary> 27 public class frog : animal 28 { 29 }
当用户使用派生类鸡的游泳方法时,由于基类的游泳方法满足鸡的需求,则直接调用基类的游泳方法返回不会;当用户使用派生类狗和蛙的游泳方法时,由于基类的游泳方法的实现不满足狗和蛙的需求,所以狗和蛙需要实现自己的逻辑。如何实现狗的狗刨和蛙的蛙泳?答案是重写(使用关键字virtual和override在派生类中重新实现基类的同名方法)。
1 public class animal 2 { 3 //设计基类时对需要在派生类中重写的方法使用virtual修饰,使用virtual修饰的方法叫做虚方法 4 public virtual string swimming() 5 { 6 return "不会"; 7 } 8 } 9 public class dog : animal 10 { 11 //派生类若要重写基类的方法,可使用override修饰与基类同名的方法,实现自己的行为,被override修饰的方法也可被重写 12 public override string swimming() 13 { 14 return "狗刨"; 15 } 16 } 17 public class frog : animal 18 { 19 //派生类若要重写基类的方法,可使用override修饰与基类同名的方法,实现自己的行为,被override修饰的方法也可被重写 20 public override string swimming() 21 { 22 return "蛙泳"; 23 } 24 }
上面的例子中,不同的派生类(鸡、狗、蛙)对基类(动物)的游泳方法表现出各自不同的结果,即类的多态特性。
也可以使用另一种方式遮挡来实现类的多态特性,即派生类使用new关键字来实现与基类同名方法的不同行为。
1 public class animal 2 { 3 public string swimming() 4 { 5 return "不会"; 6 } 7 } 8 public class dog : animal 9 { 10 //使用new关键字遮挡基类的同名方法,也可以使用到派生类的其他成员中 11 public new string swimming() 12 { 13 return "狗刨"; 14 } 15 } 16 public class frog : animal 17 { 18 //使用new关键字遮挡基类的同名方法,也可以使用到派生类的其他成员中 19 public new string swimming() 20 { 21 return "蛙泳"; 22 } 23 }
注:主要使用的情况是当我们没有修改基类的权限又希望实现派生类的多态特性时。
二、c#关键字:base
在派生类的方法中使用“base.基类方法名”可以复用基类的方法。
三、c#关键字:sealed
由于被override修饰的方法是隐式可重写的,所以当我们不希望被override修饰的方法被重写时,可以使用sealed关键字防止被重写。
1 public class dog : animal 2 { 3 //dog类不希望自己的游泳方法被它的派生类重写 4 public sealed override string swimming() 5 { 6 return "狗刨"; 7 } 8 }
注:在防止被重写中,sealed关键字必须与override关键字相同存在。
四、抽象类和抽象方法
当一个基类的作用只是为派生类提供公共成员,而无其他实际意义时,我们不希望用户通过new关键字创建这个基类,可将基类设计成抽象类,这样用户就不能用new关键字创建它。使用abstract关键字修饰类可使其变成抽象类。
当抽象类的某个方法在派生类中表现出多态性时,这个方法的实现对派生来说是无用的,我们希望所有派生类必须重写基类的这个方法,可将这个方法设计成抽象方法,这样抽象类的抽象方法不用提供默认实现并且派生类必须使用override重写此抽象方法。如果派生类不重写这个抽象方法自身也将成为一个抽象类。使用abstract关键字修饰方法可使其变成抽象方法。
1 /// <summary> 2 /// 可调度温度的电子设备 3 /// </summary> 4 public abstract class temperatureelectric 5 { 6 protected int temperature; 7 8 public abstract int up(); 9 10 public abstract int down(); 11 } 12 /// <summary> 13 /// 空调 14 /// </summary> 15 public class airconditioner : temperatureelectric 16 { 17 public override int up() 18 { 19 if (temperature < 30) 20 temperature += 1; 21 return temperature; 22 } 23 24 public override int down() 25 { 26 if (temperature > 16) 27 temperature -= 1; 28 return temperature; 29 } 30 } 31 /// <summary> 32 /// 冰箱 33 /// </summary> 34 public class refrigerator : temperatureelectric 35 { 36 /// <summary> 37 /// 提升冷藏温度 38 /// </summary> 39 public override int up() 40 { 41 if (temperature < 7) 42 temperature += 1; 43 return temperature; 44 } 45 46 /// <summary> 47 /// 降低冷藏温度 48 /// </summary> 49 public override int down() 50 { 51 if (temperature > 3) 52 temperature -= 1; 53 return temperature; 54 } 55 }
五、几种概念的对比
1、重载与重写
重载和重写的相同点在于他们都是对同名的方法使用。
重载和重写的不同点:在使用范围上,前者使用在单个类中,后者使用在两个或多个有继承关系的类中;在使用意图上,前者是写法上的简化,后者是功能上的扩展。
2、重写与遮挡
重写与遮挡的相同点在于他们都是对基类同名方法的扩展。
重写与遮挡的不同点在于,通过a a = new b();b继承于a,前者调用b的同名方法,而后者调用的是a的同名方法;前者用于主动设计,后者用于被动修改。
1 public class realizeobject 2 { 3 public void realize() 4 { 5 //dog是通过virtual重写的animal 6 animal animal = new dog(); 7 animal.swimming();//输出的狗刨 8 9 //dog是通过new遮挡的animal 10 animal animal = new dog(); 11 animal.swimming();//输出的不会 12 } 13 }
3、虚方法和抽象方法
虚方法与抽象方法的相同点在于他们都可以被重写。
虚方法与抽象方法的不同点:在使用范围上前者大于后者,前者使用在非密封类中,后者只能使用在抽象类中;在使用方式上,前者必须有实现部分,后者不能有实现部分;在派生类中,前者可重写可不重写,后者必须重写。
4、抽象类与接口类型的区别(见c#接口类型)