• C#面向对象三大特性:多态


    什么是多态

    公司最近为了陶冶情操,养了几种动物(Animal),有猫(Cat)、狗(Dog)、羊(Sheep),这些动物都有共同的特性,会吃(Eat)、会叫(Shout),但是它们吃的不同,叫的也不同。既然这样,我们能不能设计一个动物类(Animal)和它的成员(Eat方法、Shout方法)来表示这些动物的共同特征,而当我们关注猫时,猫来实现这两个成员(吃鱼、喵喵叫);当我们关注狗时,狗来实现这两个成员(吃肉和汪汪叫)。

    上述例子就是一个典型的多态,就是父类的一些成员,子类继承后去重写从而实现不同的功能。

    定义:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。这就是多态,这种特性称为多态性。

    多态的优缺点

    好处:

    简化了编程接口。它容许在类和类之间重用一些习惯性的命名,而不用为每一个新加的函数命名一个新名字。

    简化代码。如果函数/方法参数中使用的是父类的类型,可以传入父类、子类的对象

    局限性:

    父类类型的变量,不能直接调用子类特有的方法。必须强转为子类类型的变量,才能直接调用子类特有的方法

    注意:子类中如果重写了父类的方法(多态),那么父类中的这个方法将不会再调用。

    多态的分类

    多态性分为两种,一种是编译时的多态性,一种是运行时的多态性。

    编译时的多态性(重载):编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。

    运行时的多态性(重写):运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中运行时的多态性是通过覆写虚成员实现。 

    多态的实现

    编译时多态:重载(overload)

    重载(overload):重载指的是同一个类中有两个或多个名字相同但是参数(参数签名)不同的方法,(注:返回值不能区别函数是否重载),重载没有关键字。

    注意:

    A.从重载的定义来看,重载是一种编译时多态

    B.重载不需要事先定义可重载的方法,即没有关键字

    C.重载只是针对一个类内部的几个参数不同,名称相同的方法。

    我们还是用那几只陶冶情操的动物来示例说明,代码如下:

    /// <summary>
    /// 狗(多态:重载事例)
    /// </summary>
    class Dog
    {
        /// <summary>
        ////// </summary>
        public void Shout()
        {
            Console.WriteLine("汪!");
        }
        /// <summary>
        /// 叫(重载方法)
        /// </summary>
        public void Shout(int count)
        {
            int i = 0;
            string shout = "";
            do
            {
                shout += "汪!";
                i++;
            } while (i <= count);
            Console.WriteLine(shout);
        }
    }
    //调用
    Dog dog = new Dog();
    dog.Shout();
    dog.Shout(5);

    运行时多态:重写

    重写有两种,一种是override修饰符,另一种使用new修饰符,下面会举例说明两种重写的使用方法和异同。

    重写(override):也称过载,重写是指子类对父类中虚函数或抽象函数的“覆盖”(这也就是有些书将过载翻译为覆盖的原因),但是这种“覆盖”和用new关键字来覆盖是有区别的。

    /// <summary>    
    /// 动物类(父类)
        /// </summary>
        class Animal
        {
           /// <summary>
           /// 名字
           /// 说明:类和子类可访问
           /// </summary>
           protected string name;
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="name"></param>
            public Animal(string name)
            {
                this.name=name;
            }
            /// <summary>
            /// 名字(虚属性)
            /// </summary>
            public virtual string MyName
            {
                get { return this.name; }           
            }
            /// <summary>
            /// 吃(虚方法)
            /// </summary>
            public virtual void Eat()
            {
                Console.WriteLine("我会吃!");
            }
            /// <summary>
            /// 叫(虚方法)
            /// </summary>
            public virtual void Shout()
            {
                Console.WriteLine("我会叫!");
            }
        }
        /// <summary>
        /// 狗(子类)
        /// </summary>
        class Dog:Animal
        {
            string myName;
            public Dog(string name): base(name)
            {
                myName = name;
            }
            /// <summary>
            /// 名字(重写父类属性)
            /// </summary>
            public override string MyName
            {
                get { return "我是:狗狗,我叫:"+this.name; }
            }
            /// <summary>
            /// 吃(重写父类虚方法)
            /// </summary>
            public  override void Eat()
            {
                Console.WriteLine("我喜欢吃肉!");
            }
            /// <summary>
            /// 叫(重写父类方法)
            /// </summary>
            public override void Shout()
            {
                Console.WriteLine("汪!汪!汪!");
            }
        }
    View Code
    //调用方法
    Animal dog = new Dog("旺财");
    string myName=dog.MyName;
    Console.WriteLine(myName);
    dog.Eat();
    dog.Shout();
    
    //运行结果如下:
    我是:狗狗,我叫:旺财
    我喜欢吃肉!
    汪!汪!汪!

    重写(new)

    new:覆盖指的是不同类中(基类或派生类)有两个或多个返回类型、方法名、参数都相同,但是方法体不同的方法。但是这种覆盖是一种表面上的覆盖,所以也叫隐藏,被覆盖的父类方法是可以调用得到的。

    /// <summary>
        /// 动物类(父类)
        /// </summary>
        class Animal
        {
           /// <summary>
           /// 名字
           /// 说明:类和子类可访问
           /// </summary>
           protected string name;
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="name"></param>
            public Animal(string name)
            {
                this.name=name;
            }
            /// <summary>
            /// 名字(虚属性)
            /// </summary>
            public virtual string MyName
            {
                get { return this.name; }           
            }
            /// <summary>
            /// 吃(虚方法)
            /// </summary>
            public virtual void Eat()
            {
                Console.WriteLine("我会吃!");
            }
            /// <summary>
            /// 叫(虚方法)
            /// </summary>
            public virtual void Shout()
            {
                Console.WriteLine("我会叫!");
            }
        }
        /// <summary>
        /// 狗(子类)
        /// </summary>
        class Dog:Animal
        {
            string myName;
            public Dog(string name): base(name)
            {
                myName = name;
            }        
            /// <summary>
            /// 名字(重写父类属性)
            /// </summary>
            public override string MyName
            {
                get { return "我是:狗狗,我叫:"+this.name; }
            }
            /// <summary>
            /// 吃(重写父类虚方法)
            /// </summary>
            public new void Eat()
            {
                Console.WriteLine("我喜欢吃肉!");
            }
            /// <summary>
            /// 叫(重写父类方法)
            /// </summary>
            public new void Shout()
            {
                Console.WriteLine("汪!汪!汪!");
            }
        }
    View Code
    //调用方法 使用new重写,则只调用父类的方法
    Animal dog = new Dog("旺财");
    string myName=dog.MyName;
    Console.WriteLine(myName);
    dog.Eat();
    dog.Shout();
    
    //执行结果如下:
    我是:狗狗,我叫:旺财
    我会吃!
    我会叫!

    如下改一下调用方法:

    //调用方法 
    Dog dog = new Dog("旺财");
    string myName=dog.MyName;
    Console.WriteLine(myName);
    dog.Eat();
    dog.Shout();
    
    //执行结果如下:
    我是:狗狗,我叫:旺财!
    我爱吃肉!
    汪!汪!汪!

    可以看出,当派生类Dog的Eat()方法使用new修饰时,Dog的对象转换为Animal对象后,调用的是Animal类中的Eat()方法。其实可以理解为,使用new关键字后,使得Dog中的Eat()方法和Animal中的Eat()方法成为毫不相关的两个方法,只是它们的名字碰巧相同而已。所以, Animal类中的Eat()方法不管用还是不用virtual修饰,也不管访问权限如何,或者是没有,都不会对Dog的Eat()方法产生什么影响(只是因为使用了new关键字,如果Dog类没用从Animal类继承Eat()方法,编译器会输出警告)。 

    严格的说,不能说通过使用new来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。

    要点:

    1. 多态是面向对象的重要特性之一,指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

    2. 多态分为两种:一种是编译时多态,使用重载实现;另一种是运行时多态,使用重写实现;

    3. 重写有两种,一种使用override关键词,另一种使用new关键词

    4. new重写实际上是对父类方法的隐藏,被覆盖的父类方法可以调用得到。因此new可以重写(或说是隐藏)的父类方法不一定要定义为虚方法或抽象方法。只是如果父类方法是虚方法或抽象方法时会覆盖父类方法,如果不是,则隐藏。

    5. 当用子类创建父类的时候,如C1 c = new C2(),重写会改变父类的功能,即调用子类的功能;而覆盖(new)不会,仍然调用父类功能

    6. 重载和覆盖的发生条件:

      重载,必然发生在一个类中,函数名相同,参数类型或者顺序不同构成重载,与返回类型无关

      重写,必然发生在基类和派生类中,其类函数用virtual修饰,派生类用override修饰

      覆盖,在子类中写一个和基类一样名字(参数不同也算)的非虚函数,会让基类中的函数被隐藏,编译后会提示要求使用New关键字 new 修饰隐藏,在子类中可以通过new 隐藏父类的方法

    7. virtual修饰符不能与private、static、abstract或者override修饰符同时使用

    8. override修饰符不能与new、static或者virtual修饰符同时使用,并且重写方法只能用于重写基类中的虚方法

    f.new覆盖与重写、重载的区别:

    当子类与父类的参数不同时

      当基类函数不是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载)

      当基类函数是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载;因为参数不同,所以不是重写)

    当子类与父类的参数相同时

      当基类函数不是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载,因为基类不是虚函数,所以是隐藏不是重写)

      当基类函数是虚函数时,基类函数将被覆盖。(因为子类和基类不在同一范围内,所以不是重载)

    普通类多态Eg:

    using System;
    
    namespace Traffic
    {
        class Animal
        {
            public virtual void Breed()//定义 public 修饰的 virtual 虚方法 
            {
                Console.WriteLine("动物需要交配来繁殖后代");
            }
            protected virtual void Breed2()//定义 protected 修饰的 virtual 虚方法 
            {
                Console.WriteLine("动物需要交配来繁殖后代");
            }
            public void DoBreed()
            {
                this.Breed2();
            }
        }
    
        class Mantis : Animal //继承自动物类的螳螂类 
        {
            public override void Breed()//override重写父类Animal类型定义的繁殖方法 
            {
                Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
            }
    
            protected override void Breed2()//override重写父类Animal类型定义的繁殖方法 
            {
                Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
            }
        }
    
        class Chook : Animal //继承自动物类的鸡类 
        {
            public override void Breed()//override重写父类Animal类型定义的繁殖方法 
            {
                Console.WriteLine("鸡生蛋来繁殖后代");
            }
    
            protected override void Breed2()//override重写父类Animal类型定义的繁殖方法 
            {
                Console.WriteLine("鸡生蛋来繁殖后代");
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Animal animal = new Animal();
                animal.Breed();
    
                Mantis mantis = new Mantis();//创建 mantis 对象 
                if (mantis is Animal)//is关键字判断对象 mantis 是否属于 Animal 类 
                {
                    Animal mantisAnimal = mantis as Animal;//as 关键字将 mantis 对象转换为 Animal 类型对象 
                    mantisAnimal.Breed();//调用Animal类型对象的 Bread 方法
                }
    
                Chook chook = new Chook();
                if (chook is Animal)
                {
                    Animal chookAnimal = chook as Animal;
                    chook.Breed();
                }
                Console.WriteLine(Environment.NewLine);//空行间隔 
    
                Animal animal2 = new Animal();
                //animal2.DoBreed(); 
                Breed(animal2);
                Mantis mantis2 = new Mantis();
                //mantis2.DoBreed(); 
                Breed(mantis2);
                Chook chook2 = new Chook();
                chook2.DoBreed();
                //Breed(chook2);
            }
    
            static void Breed(Animal animal)//定义参数类型为父类型的 Breed 方法 
            {
                animal.DoBreed();
            }
        }
    }
    View Code

    代码分析:

    1.定义 Mantis 和 Chook 类的时候重写了 Breed 方法

    2.Animal mantisAnimal = mantis as Animal;//as 关键字将 mantis 对象转换为 Animal 类型对象,此时并未创建新的对象,只是将原来的对象转换了类型,mantis 和 mantisAnimal 指向同一个对象

    3.代码示例是以两种不同的方式,一种是简单示例,一种是复杂示例

    运行结果:

    抽象类多态:             

    抽象类不能实例化,抽象方法只能在抽象类中定义,在定义抽象类的子类时必须将抽象类中定义的抽象方法重写。

    抽象类多态Eg:

    using System;
    
    namespace Traffic
    {
        abstract class Animal
        {
            public abstract void Breed();//定义 public 修饰的 abstract 抽象方法  
            protected abstract void Breed2();//定义 protected 修饰的 abstract 虚方法 
            public void DoBreed() 
            { 
                this.Breed2(); 
            }
        }
    
        class Mantis : Animal //继承自动物类的螳螂类 
        {
            public override void Breed()//override重写父类Animal类型定义的繁殖方法
            {
                Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
            }
    
            protected override void Breed2()//override重写父类Animal类型定义的繁殖方法
            {
                Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
            }
        }
    
        class Chook : Animal //继承自动物类的鸡类 
        {
            public override void Breed()//override重写父类Animal类型定义的繁殖方法
            {
                Console.WriteLine("鸡生蛋来繁殖后代");
            }
    
            protected override void Breed2()//override重写父类Animal类型定义的繁殖方法
            {
                Console.WriteLine("鸡生蛋来繁殖后代");
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Mantis mantis = new Mantis();//创建 mantis 对象
                if (mantis is Animal)//is关键字判断对象 mantis 是否属于 Animal 类
                {
                    Animal mantisAnimal = mantis as Animal;//as 关键字将 mantis 对象转换为 Animal 类型对象
                    mantisAnimal.Breed();//调用Animal类型对象的 Bread 方法
                }
    
                Chook chook = new Chook();
                if (chook is Animal)
                {
                    Animal chookAnimal = chook as Animal;
                    chook.Breed();
                }
                Console.WriteLine(Environment.NewLine);//空行间隔
    
                Mantis mantis2 = new Mantis();
                //mantis2.DoBreed();
                Breed(mantis2);
                Chook chook2 = new Chook();
                chook2.DoBreed();
                //Breed(chook2);
            }
    
            static void Breed(Animal animal)//定义参数类型为父类型的 Breed 方法 
            {
                animal.DoBreed();
            }
        }
    }
    View Code

    运行结果:

    接口多态Eg:

    using System;
    
    namespace Traffic
    {
        interface IAnimal
        {
            void Breed();//声明接口成员
        }
    
        class Mantis : IAnimal //继承自接口的螳螂类
        {
            public void Breed()//类继承接口后,必须将接口声明的全部成员都实现
            {
                Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
            }
        }
    
        class Chook : IAnimal //继承自接口的鸡类
        {
            public void Breed()
            {
                Console.WriteLine("鸡生蛋来繁殖后代");
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Mantis mantis = new Mantis();//创建 mantis 对象
                if (mantis is IAnimal)//is关键字判断对象 mantis 是否属于 Animal 类
                {
                    IAnimal imantis = mantis as IAnimal;//as 关键字将 mantis 对象转换为 Animal 类型对象
                    imantis.Breed();//调用Animal类型对象的 Bread 方法
                }
    
                IAnimal ichook = new Chook();//创建鸡对象后直接转换为 IAnimal 接口类型变量 ichook
                ichook.Breed();
                Console.WriteLine(Environment.NewLine);//空行间隔
    
                Mantis mantis2 = new Mantis();
                Breed(mantis2);
                Chook chook2 = new Chook();
                Breed(chook2);
    
                Console.ReadLine();
            }
    
            static void Breed(IAnimal animal)//定义参数类型为父类型的 Breed 方法 
            {
                animal.Breed();
            }
        }
    }
    View Code

    运行结果:

     

    原文链接:https://www.cnblogs.com/qinyi173/p/4685947.html

    原文链接:http://blog.sina.com.cn/s/blog_bc7f750001016gg4.html

  • 相关阅读:
    面试题:使用存储过程造10w条测试数据,数据库插入10w条不同数据
    面试题:如何造10w条测试数据,在数据库插入10w条不同数据
    httprunner 2.x学习9-生成测试报告ExtentReport('dict object' has no attribute 'meta_data'问题已解决)
    httprunner 2.x学习8-参数化(引用 debugtalk 函数)
    httprunner 2.x学习7-参数化(引用外部csv数据)
    httprunner 2.x学习6-参数化与数据驱动
    httprunner 2.x学习5-测试用例集(testsuite)
    httprunner 2.x学习4-测试用例分层
    启动Hadoop HA Hbase zookeeper spark
    如何运行Spark程序
  • 原文地址:https://www.cnblogs.com/zhaoyl9/p/11612012.html
Copyright © 2020-2023  润新知