(六)C#基础加强系列之“ 多态之里氏转换原则、隐藏重写基类方法 ”篇
首先,大家记住两点:
(1):子类可以直接赋值给父类,也就是说,子类可以直接转换成父类。
(2):指向子类的父类,可以强制转换为对应的子类。
我们先来谈谈第一点,什么叫做子类可以直接赋值给父类呢?我们配合代码来看:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _01里氏转换原则 { class Program { static void Main(string[] args) { Chinese c1 = new Chinese();//创建一个 Chinese 对象 c1.Name = "Mr Lucky"; Person p1 = c1;//子类直接赋值给父类 } } class Person//定义一个Person类 所有类的父类 { string name;//定义一个 name 字段 public string Name { get { return name; } set { name = value; } } } class Chinese : Person//定义一个类,中国人 继承自Person类 { public void SayHello() { Console.WriteLine("你好,我是中国人!"); } } class American : Person//定义一个类,美国人 继承自Person类 { public void SayHello() { Console.WriteLine("你好,我是美国人!"); } } class Japanese : Person//定义一个类,日本人 继承自Person类 { public void SayHello() { Console.WriteLine("你好,我是日本人!"); } } }
我们创建了一个 Person 父类,后面分别写了3个子类,都继承自 Person 类,接着,在主方法中创建了一个 Chinese 类的对象,并将继承自父类的字段 name 赋值,最后,直接将创建好了的对象 c1 赋值给 Person 类,也就是直接将子类直接赋值给父类,调试代码发现,没有任何错误,这也就实现了里氏转换原则中的 “子类可以直接赋值给父类”。
我们创建了三个子类,如何统一管理呢?总不能new 一个子类,然后再 Person p=XX XX,这样很麻烦,我们还可以用数组将他们统一起来:
static void Main(string[] args) { Chinese c1 = new Chinese(); c1.Name = "幸运先生"; American a1 = new American(); a1.Name = "Mr lucky"; Japanese J1 = new Japanese(); J1.Name = "にねのせ"; Person[] per = { c1,a1,J1}; }
现在,我们将 Chinese 类, American 类, Japanese 类都赋给了 Person 类,那么,现在这三个子类是什么类型的呢?在这里,三个子类就是 Person 类型的了。
还要和大家说明的是,父类只能访问父类的成员,我们看图:
在里氏转换原则第一条当中,子类直接可以赋值给父类,所以可以直接将 c1、a1、J1 存入 person类型 的数组当中,首先我们知道 Person[0] 代表的是 Chinese 类,也就是 c1 ,此时的 c1 因为直接赋值给了父类,所以是 Person 类型,也就造成了我们在用 per[0] 点的时候,只点出了 name 字段的属性 Name,而 Chinese 中的 SayHello() 方法是无法点出来的,这也就是我刚才说的,父类只能访问父类的成员。
但是我现在就想调用子类中 SayHello(),怎么办呢? 这就使用到了我们里氏转换原则中的第二条,指向子类的父类,可以强制转换为对应的子类。强制转换,大家应该都会吧!
看下面如何进行强制转换:
Person[] per = { c1,a1,J1}; //per[0]. 父类只能访问父类的成员 无法访问子类的成员,想要访问 可以进行强制转换 里氏原则中的第二条 ((Chinese)per[0]).SayHello(); Console.ReadKey();
得到的结果:
里氏原则第二条,指向子类的父类,可以强制转换为对应的子类,当我们进行强制转换以后,那么现在的 per[0] 就不在是 person 类型的了,而是 Chinese 类型的,所以我们可以点出 SayHello() 方法。
我把代码改一下:
((American)per[0]).SayHello();
上面是将 per[0] 强制转换成 Chinese 类,我们发现是可以的,那么,当我改成 American 的时候,可不可以呢!调试一下:
调试之后,发现报错了…… 为什么可以强制转换成 Chinese 类型,不可以强制转换成 American 类型呢?我们来分析一下:
再看一遍里氏转换原则第二点:
指向子类的父类,可以强制转换为对应的子类。
仔细看,可以强制转换为 对应 的子类,我将 对应 加粗了一下并下划线,记住:对应、对应 。
在 per[0] 这块区域中我们预存的是 Chinese 类型的对象 c1,并不是 American 类型的 a1,我们想将 per[0] 强制转换,就只能转换成 Chinese 类型,因为我们原本在 per[0]中存的就是 Chinese 类型的对象,这也遵循了里氏转换原则第二条“指向子类的父类,可以强制转换为对应的子类”,记住,对应,对应。
下面的 per[1] 、per[2] 也只能对应的强制转换成 American 类型 和 Japanese 类型。
这边可能比较绕,大家多思考思考。
让 c1、a1、J1这三个人向辛苦学习的朋友们和博主我打个招呼吧!
Person[] per = { c1,a1,J1}; //per[0]. 父类只能访问父类的成员 无法访问子类的成员,想要访问 可以进行强制转换 里氏原则中的第二条 //((American)per[0]).SayHello(); ((Chinese)per[0]).SayHello(); ((American)per[1]).SayHello(); ((Japanese)per[2]).SayHello(); Console.ReadKey();
打完招呼,我们继续切入正题吧!
我们想,现在只有三个人和我们打招呼,还是有可行性的,但是,如果是100个人呢?1000个人呢?我们总不能写100或者1000个强制转换吧!
for循环?? ok! 我们来写一个:
for (int i = 0; i < per.Length; i++) { if(per[i] 是 Chinese) { ((Chinese)per[0]).SayHello(); } else if(per[i] 是 American) { ((American)per[1]).SayHello(); } else { ((Japanese)per[2]).SayHello(); } }
这是我们想象的,这只是伪代码。真实的代码如下:把 “是” 改成 is:
is运算符:用来判断父类对象是否可以强制转换为子类对象 对象 is 类型名 。 如果可以转换,则返回 true ,否则 ,返回 false。
for (int i = 0; i < per.Length; i++) { if(per[i] is Chinese) { ((Chinese)per[0]).SayHello(); } else if(per[i] is American) { ((American)per[1]).SayHello(); } else { ((Japanese)per[2]).SayHello(); } }
得到的结果:
用 for 循环 和 is,我们很有效的减少了代码的书写量。
这边我们写的是三个国家的人,全球180多个国家呢?我们后面再讲这个问题。
再来讲解讲解继承的特征:
》》单根性:
所有的类,只能继承自一个类,不能继承多个类。也就是说所有的类都只有一个父类,但是,可以实现多个接口(接口后面会详细讲解)。
》》派生于 object 类:
在 C# 中, object 类是基类,所有的类型都来源于 object ,反编译看看我们常用的 Console、int、string:
Console:
int32:
string:
》》传递性:
孙子类继承自父亲类,父亲类继承自爷爷类,爷爷类继承自太爷爷类。
孙子类照样有太爷爷的特征。这就是继承的传递性。
现在有一个问题,子类和父类同时具有相同名字的方法,能不能有呢?
来看代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _02隐藏基类方法 { class Program { static void Main(string[] args) { BaseClass c1 = new BaseClass();//创建一个父类对象 c1.Func(); Class c2 = new Class();//创建一个子类对象 c2.Func(); Console.ReadKey(); } } class BaseClass // 创建一个父类 { public void Func()//和子类相同的方法 { Console.WriteLine("我是父类的方法"); } } class Class : BaseClass// 创建一个子类继承自BaseClass { public void Func()//和父类相同的方法 { Console.WriteLine("我是子类的方法"); } } }
得到的结果:
问题的答案是能的。
但是,子类中的 Func 爆出绿色波浪线和警告的内容:
我们只需要在方法中添加一个 new ,这就代表要隐藏父类中的方法,如果我们不写,系统会默认帮我们添加上。
什么叫做隐藏父类的方呢?大个比方:
墙上有一幅画,现在用一块布将这幅画遮起来,那么我们现在只能看到布,而看不到画了,但是,这幅画还是存在的,只是被隐藏了,父类中的方法好比墙上的画,子类中的方法就好比布,将父类中的方法遮盖起来了。
当子类与父类有相同的方法(非重载)时,此时创建子类对象,称为子类隐藏了父类的方法。
这和多态有什么关系呢?
同一件对象在不同的环境下表现出不同的状态。这就是多态表现的一种形式(并不是只有这一种表现形式)。
看代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _03隐藏基类方法 { class Program { static void Main(string[] args) { Home hm1 = new Person();//创建一个 hm1 对象,并让这个对象指向 Person 类型 hm1.Work(); ((School)hm1).Work(); ((Computer)hm1).Work(); ((Person)hm1).Work(); Console.ReadKey(); } } class Home//创建一个类 代表着在家里的环境 { public void Work() { Console.WriteLine("吃饭、睡觉、打豆豆"); } } class School : Home//创建一个类 代表着在学校里的环境 继承自 Home { public new void Work() { Console.WriteLine("上课"); } } class Computer : School//创建一个类 代表着在电脑里的环境 继承自 School { public new void Work() { Console.WriteLine("写程序"); } } class Person : Computer//创建一个类 代表着在一个人 继承自 Computer { public new void Work() { Console.WriteLine("活着!!!"); } } }
得到的结果为:
在这里,同一对象,代表着 hm1 ,Home、School、Computer代表着不同的环境,所得到的结果,表明在不同环境下所表现出来的不同的状态。
现在有人可能要问,博主前面不是说,指向子类的父类,可以强制转换成对应的子类,在这里new出来的只有Person类啊,应该是只能强制转换成Person类型啊!!在这里要注意的是,Person类继承自Computer类,Computer类继承自School类,School类继承自work类,所以,因为继承的原因,所以都是可以进行强制转换的。
另外,因为所有的子类和对应的父类中的方法一致,也就导致了子类会默认隐藏父类中的方法,所以就很好的实现了 “同一件对象在不同的环境下表现出不同的状态” 这一多态的实现。
刚才讲的是隐藏父类中的方法,接下来再讲重写基类中的方法。
什么叫做重写父类的方法呢?和多态又有什么关系呢?
打个比方,墙上有一幅画,现在把墙砸了,画也就不存在了。新盖了一个墙,重写画了了一幅画。原本墙上的一幅画好比父类中的方法,新盖的墙上的画代表这子类中的方法,新的画代替了老的画。
不同的对象在相同的环境下所表现出不同的状态。这也是一种多态的表现形式 。
重写父类方法的步骤:
(1):在父类方法前加上一个 virtual
(1):在子类方法前加上一个 override
看代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _04重写基类方法 { class Program { static void Main(string[] args) { USB usb = new USB();//创建一个USB对象 指向USB usb.Insert(); usb = new UDisk();//让USB对象 指向UDisk usb.Insert(); usb = new Fan();//让USB对象 指向Fan usb.Insert(); Console.ReadKey(); } } class USB // 创建一个父类 表示USB协议 { public virtual void Insert() { Console.WriteLine("我是USB协议"); } } class UDisk : USB// 创建一个子类继承自USB 表示U盘 { public override void Insert() { Console.WriteLine("我是U盘"); } } class Fan : USB// 创建一个子类继承自USB 表示USB风扇 { public override void Insert() { Console.WriteLine("我是USB风扇"); } } }
得到的结果:
在这里,不同对象,代表着 UDisk、Fan,USB协议代表着相同的环境,所得到的结果,表明在相同环境下所表现出来的不同的状态(统一调用,不同实现)。
要提醒大家的是,是重写父类中的方法,而不是重写子类中继承父类中的方法。非常的绕,这里比较难理解。
再回到上面的问题:
全球180多个国家呢?我们怎么办呢,可以用多态实现。
大家看代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _05用多态解决多个国家的问题 { class Program { static void Main(string[] args) { Chinese c1 = new Chinese(); c1.Name = "幸运先生"; American a1 = new American(); a1.Name = "Mr lucky"; Japanese J1 = new Japanese(); J1.Name = "にねのせ"; Person[] per = { c1, a1, J1 }; for (int i = 0; i < per.Length; i++) { per[i].SayHello(); } Console.ReadKey(); } } class Person//定义一个Person类 所有类的父类 { string name;//定义一个 name 字段 public string Name { get { return name; } set { name = value; } } public virtual void SayHello()//添加一个可以被重写的方法 { } }
class Chinese : Person//定义一个类,中国人 继承自Person类 { public override void SayHello() { Console.WriteLine("你好,我是中国人!"); } } class American : Person//定义一个类,美国人 继承自Person类 { public override void SayHello() { Console.WriteLine("你好,我是美国人!"); } } class Japanese : Person//定义一个类,日本人 继承自Person类 { public override void SayHello() { Console.WriteLine("你好,我是日本人!"); } } }
只需在父类中添加一个可以被重写的方法,让子类循环的重写父类中的方法即可。
今天讲的比较多,面向对象包括封装、继承、多态,今天讲的多态是最难的部分了,当初我也是绕了很长时间才绕出来,大家继续努力吧!最后,再送给大家一道面试题吧!记得一定要做。做之前最好先把我的注释去掉,自己先做一遍。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 面试题_变态_ { class A { public string Str = "A"; public void Show() { Console.WriteLine("Show A"); } } class B : A { public string Str = "B"; //隐藏了A里面的方法 public virtual void Show() { Console.WriteLine("Show B"); } //virtual表示方法可以被重写 } class C : B { public override void Show() { Console.WriteLine("Show C"); } //继承了B的字段,但是重写了B的方法 override表示重写了B的方法 } class D : C { public string Str = "D"; //隐藏了C的方法 public void Show() { Console.WriteLine("Show D"); } } class Program { static void Main(string[] args) { D d = new D(); //new的是自己D类型的对象 Console.WriteLine(d.Str)的结果是D, d.Show()的结果是 show D C c = d; //等价于C c=new d(里氏转换) Console.WriteLine(c.Str)的结果是B, c.Show()的结果是 Show C B b = d; //等价于B b=new d(里氏转换) Console.WriteLine(b.Str)的结果是B, b.Show()的结果是 Show C A a = d; //等价于D d=new d(里氏转换) Console.WriteLine(a.Str)的结果是A, a.Show()的结果是 Show A Console.WriteLine(d.Str); Console.WriteLine(c.Str); Console.WriteLine(b.Str); Console.WriteLine(a.Str); Console.WriteLine("------------"); d.Show(); c.Show(); b.Show(); a.Show(); Console.ReadLine(); } } }
革命尚未成功,同志们人需努力啊!!!