(六)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();
        }
    }
}

 

 革命尚未成功,同志们人需努力啊!!!

posted @ 2012-11-04 01:18  乐文  阅读(995)  评论(4编辑  收藏  举报