C# 多态(虚方法,抽象,接口实现)

浅谈C# 多态的魅力(虚方法,抽象,接口实现)

前言:我们都知道面向对象的三大特性:封装,继承,多态。封装和继承对于初学者而言比较好理解,但要理解多态,尤其是深入理解,初学者往往存在有很多困惑,为什么这样就可以?有时候感觉很不可思议,由此,面向对象的魅力体现了出来,那就是多态,多态用的好,可以提高程序的扩展性。常用的设计模式,比如简单工厂设计模式,核心就是多态。

其实多态就是:允许将子类类型的指针赋值给父类类型的指针。也就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。如果这边不理解可以先放一放,先看下面的事例,看完之后再来理解这句话,就很容易懂了。
理解多态之前首先要对面向对象的里氏替换原则和开放封闭原则有所了解。

里氏替换原则(Liskov Substitution Principle):派生类(子类)对象能够替换其基类(超类)对象被使用。通俗一点的理解就是“子类是父类”,举个例子,“男人是人,人不一定是男人”,当需要一个父类类型的对象的时候可以给一个子类类型的对象;当需要一个子类类型对象的时候给一个父类类型对象是不可以的!

开放封闭原则(Open Closed Principle):封装变化、降低耦合,软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。因此,开放封闭原则主要体现在两个方面:对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。

对这两个原则有一定了解之后就能更好的理解多态。

首先,我们先来看下怎样用虚方法实现多态

我们都知道,喜鹊(Magpie)、老鹰(Eagle)、企鹅(Penguin)都是属于鸟类,我们可以根据这三者的共有特性提取出鸟类(Bird)做为父类,喜鹊喜欢吃虫子,老鹰喜欢吃肉,企鹅喜欢吃鱼。

创建基类Bird如下,添加一个虚方法Eat():

    /// <summary>
    /// 鸟类:父类
    /// </summary>
    public class Bird
    {
        /// <summary>
        /// 吃:虚方法
        /// </summary>
        public virtual void Eat()
        {
            Console.WriteLine("我是一只小小鸟,我喜欢吃虫子~");
        }
    }

创建子类Magpie如下,继承父类Bird,重写父类Bird中的虚方法Eat():

    /// <summary>
    /// 喜鹊:子类
    /// </summary>
    public  class Magpie:Bird
    {
        /// <summary>
        /// 重写父类中Eat方法
        /// </summary>
        public override void Eat()
        {
            Console.WriteLine("我是一只喜鹊,我喜欢吃虫子~");
        }
    }

创建一个子类Eagle如下,继承父类Bird,重写父类Bird中的虚方法Eat():

    /// <summary>
    /// 老鹰:子类
    /// </summary>
    public  class Eagle:Bird
    {
        /// <summary>
        /// 重写父类中Eat方法
        /// </summary>
        public override void Eat()
        {
            Console.WriteLine("我是一只老鹰,我喜欢吃肉~");
        }
    }

创建一个子类Penguin如下,继承父类Bird,重写父类Bird中的虚方法Eat():

    /// <summary>
    /// 企鹅:子类
    /// </summary>
    public  class Penguin:Bird
    {
        /// <summary>
        /// 重写父类中Eat方法
        /// </summary>
        public override void Eat()
        {
            Console.WriteLine("我是一只小企鹅,我喜欢吃鱼~");
        }
    }

到此,一个基类,三个子类已经创建完毕,接下来我们在主函数中来看下多态是怎样体现的。

    static void Main(string[] args)
    {
        //创建一个Bird基类数组,添加基类Bird对象,Magpie对象,Eagle对象,Penguin对象
        Bird[] birds = {
                       new Bird(),
                       new Magpie(),
                       new Eagle(),
                       new Penguin()
        };
        //遍历一下birds数组
        foreach (Bird bird in birds)
        {
            bird.Eat();
        }
        Console.ReadKey();
    }

运行结果:

由此可见,子类Magpie,Eagle,Penguin对象可以赋值给父类对象,也就是说父类类型指针可以指向子类类型对象,这里体现了里氏替换原则。

父类对象调用自己的Eat()方法,实际上显示的是父类类型指针指向的子类类型对象重写父类Eat后的方法。这就是多态。

多态的作用到底是什么呢?
其实多态的作用就是把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
以上程序也体现了开放封闭原则,如果后面的同事需要扩展我这个程序,还想再添加一个猫头鹰(Owl),很容易,只需要添加一个Owl类文件,继承Bird,重写Eat()方法,添加给父类对象就可以了。至此,该程序的扩展性得到了提升,而又不需要查看源代码是如何实现的就可以扩展新功能。这就是多态带来的好处。

我们再来看下利用抽象如何来实现多态

还是刚才的例子,我们发现Bird这个父类,我们根本不需要使用它创建的对象,它存在的意义就是供子类来继承。所以我们可以用抽象类来优化它。
我们把Bird父类改成抽象类,Eat()方法改成抽象方法。代码如下:

    /// <summary>
    /// 鸟类:基类
    /// </summary>
    public abstract class Bird
    {
        /// <summary>
        /// 吃:抽象方法
        /// </summary>
        public abstract void Eat();
    }

抽象类Bird内添加一个Eat()抽象方法,没有方法体。也不能实例化。
其他类Magpie,Eagle,Penguin代码不变,子类也是用override关键字来重写父类中抽象方法。
Main主函数中Bird就不能创建对象了,代码稍微修改如下:

        static void Main(string[] args)
        {
            //创建一个Bird基类数组,添加 Magpie对象,Eagle对象,Penguin对象
            Bird[] birds = {
                           new Magpie(),
                           new Eagle(),
                           new Penguin()
            };
            //遍历一下birds数组
            foreach (Bird bird in birds)
            {
                bird.Eat();
            }
            Console.ReadKey();
        }

执行结果:

由此可见,我们选择使用虚方法实现多态还是抽象类抽象方法实现多态,取决于我们是否需要使用基类实例化的对象.

比如说 现在有一个Employee类作为基类,ProjectManager类继承自Employee,这个时候我们就需要使用虚方法来实现多态了,因为我们要使用Employee创建的对象,这些对象就是普通员工对象。
再比如说 现在有一个Person类作为基类,Student,Teacher 类继承Person,我们需要使用的是Student和Teacher创建的对象,根本不需要使用Person创建的对象,
所以在这里Person完全可以写成抽象类。

总而言之,是使用虚方法,或者抽象类抽象方法实现多态,视情况而定,什么情况?以上我说的两点~

接下来~~~~

我要问一个问题,喜鹊和老鹰都可以飞,这个飞的能力,我怎么来实现呢?

XXX答:“在父类Bird中添加一个Fly方法不就好了~~”

我再问:“好的,照你说的,企鹅继承父类Bird,但是不能企鹅不能飞啊,这样在父类Bird中添加Fly方法是不是不合适呢?”

XXX答:“那就在能飞的鸟类中分别添加Fly方法不就可以了吗?”

对,这样是可以,功能完全可以实现,可是这样违背了面向对象开放封闭原则,下次我要再扩展一个鸟类比如猫头鹰(Owl),我还要去源代码中看下Fly是怎么实现的,然后在Owl中再次添加Fly方法,相同的功能,重复的代码,这样是不合理的,程序也不便于扩展;

其次,如果我还要添加一个飞机类(Plane),我继承Bird父类,合适吗?

很显然,不合适!所以我们需要一种规则,那就是接口了,喜鹊,老鹰,飞机,我都实现这个接口,那就可以飞了,而企鹅我不实现这个接口,它就不能飞~~

好,接下来介绍一下接口如何实现多态~

添加一个接口IFlyable,代码如下:

    /// <summary>
    /// 飞 接口
    /// </summary>
    public interface IFlyable
    {
        void Fly();
    }

喜鹊Magpie实现IFlyable接口,代码如下:

    /// <summary>
    /// 喜鹊:子类,实现IFlyable接口
    /// </summary>
    public  class Magpie:Bird,IFlyable
    {
        /// <summary>
        /// 重写父类Bird中Eat方法
        /// </summary>
        public override void Eat()
        {
            Console.WriteLine("我是一只喜鹊,我喜欢吃虫子~");
        }
        /// <summary>
        /// 实现 IFlyable接口方法
        /// </summary>
        public void Fly()
        {
            Console.WriteLine("我是一只喜鹊,我可以飞哦~~");
        }
    }

老鹰Eagle实现IFlyable接口,代码如下:

    /// <summary>
    /// 老鹰:子类实现飞接口
    /// </summary>
    public  class Eagle:Bird,IFlyable
    {
        /// <summary>
        /// 重写父类Bird中Eat方法
        /// </summary>
        public override void Eat()
        {
            Console.WriteLine("我是一只老鹰,我喜欢吃肉~");
        }

        /// <summary>
        /// 实现 IFlyable接口方法
        /// </summary>
        public void Fly()
        {
            Console.WriteLine("我是一只老鹰,我可以飞哦~~");
        }
    }

在Main主函数中,创建一个IFlyable接口数组,代码实现如下:

    static void Main(string[] args)
    {
        //创建一个IFlyable接口数组,添加 Magpie对象,Eagle对象
        IFlyable[] flys = {
                       new Magpie(),
                       new Eagle()
        };
        //遍历一下flys数组
        foreach (IFlyable fly in flys)
        {
            fly.Fly();
        }
        Console.ReadKey();
    }

执行结果:

由于企鹅Penguin没有实现IFlyable接口,所以企鹅不能对象不能赋值给IFlyable接口对象,所以企鹅,不能飞~

好了,刚才我提到了飞机也能飞,继承Bird不合适的问题,现在有了接口,这个问题也可以解决了。如下,我添加一个飞机Plane类,实现IFlyable接口,代码如下:

    /// <summary>
    /// 飞机类,实现IFlyable接口
    /// </summary>
    public  class Plane:IFlyable
    {
        /// <summary>
        /// 实现接口方法
        /// </summary>
        public void Fly()
        {
            Console.WriteLine("我是一架飞机,我也能飞~~");
        }
    }

在Main主函数中,接口IFlyable数组,添加Plane对象:

    class Program
    {
        static void Main(string[] args)
        {
            //创建一个IFlyable接口数组,添加 Magpie对象,Eagle对象,Plane对象
            IFlyable[] flys = {
                           new Magpie(),
                           new Eagle(),
                           new Plane()
            };
            //遍历一下flys数组
            foreach (IFlyable fly in flys)
            {
                fly.Fly();
            }
            Console.ReadKey();
        }
    }

执行结果:

由此,可以看出用接口实现多态程序的扩展性得到了大大提升,以后不管是再扩展一个蝴蝶(Butterfly),还是鸟人(Birder)创建一个类,实现这个接口,在主函数中添加该对象就可以了。
也不需要查看源代码是如何实现的,体现了开放封闭原则!

接口充分体现了多态的魅力~~

以上通过一些小的事例,给大家介绍了面向对象中三种实现多态的方式,或许有人会问,在项目中怎么使用多态呢?多态的魅力在项目中如何体现?
那么接下来我做一个面向对象的简单计算器,来Show一下多态在项目中使用吧!

加减乘除运算,我们可以根据共性提取出一个计算类,里面包含两个属性 Number1和Number2,还有一个抽象方法Compute();代码如下:

    /// <summary>
    /// 计算父类
    /// </summary>
    public abstract class Calculate
    {
        public int Number1
        {
            get;
            set;
        }
        public int Number2
        {
            get;
            set;
        }
        public abstract int Compute();
    }

接下来,我们添加一个加法器,继承计算Calculate父类:

    /// <summary>
    /// 加法器
    /// </summary>
    public class Addition : Calculate
    {
        /// <summary>
        /// 实现父类计算方法
        /// </summary>
        /// <returns>加法计算结果</returns>
        public override int Compute()
        {
            return Number1 + Number2;
        }
    }

再添加一个减法器,继承计算Calculate父类:

    /// <summary>
    /// 减法器
    /// </summary>
    public class Subtraction : Calculate
    {
        /// <summary>
        /// 实现父类计算方法
        /// </summary>
        /// <returns>减法计算结果</returns>
        public override int Compute()
        {
            return Number1 - Number2;
        }
    }

在主窗体FormMain中,编写计算事件btn_Compute_Click,代码如下:

    private void btn_Compute_Click(object sender, EventArgs e)
    {
        //获取两个参数
        int number1 = Convert.ToInt32(this.txt_Number1.Text.Trim());
        int number2 = Convert.ToInt32(this.txt_Number2.Text.Trim());
        //获取运算符
        string operation = cbb_Operator.Text.Trim();
        //通过运算符,返回父类类型
        Calculate calculate = GetCalculateResult(operation);
        calculate.Number1 = number1;
        calculate.Number2 = number2;
        //利用多态,返回运算结果
        string result = calculate.Compute().ToString();
        this.lab_Result.Text = result;
    }
    /// <summary>
    /// 通过运算符,返回父类类型
    /// </summary>
    /// <param name="operation"></param>
    /// <returns></returns>
    private Calculate GetCalculateResult(string operation)
    {
        Calculate calculate = null;
        switch (operation)
        {
            case "+":
                calculate = new Addition();
                break;
            case "-":
                calculate = new Subtraction();
                break;
        }
        return calculate;
    }

在该事件中主要调用GetCalculateResult方法,通过运算符,创建一个对应的加减乘除计算器子类,然后赋值给父类,其实这就是设计模式中的简单工厂设计模式,我给你一个运算符你给我生产一个对应的加减乘除计算器子类,返回给我。。其实大多数的设计模式的核心就是多态,掌握好多态,设计模式看起来也很轻松。

现阶段工作已经完成,但是过了一段时间,又添加新的需求了,我还要扩展一个乘法了,那好,很简单只要创建一个乘法计算器继承Calculate父类即可,看代码:

    /// <summary>
    /// 乘法计算器
    /// </summary>
    public  class Multiplication:Calculate
    {
        public override int Compute()
        {
            return Number1*Number2;
        }
    }

然后在GetCalculateResult函数中添加一个case 就好了:

    switch (operation)
    {
        case "+":
            calculate = new Addition();
            break;
        case "-":
            calculate = new Subtraction();
            break;
        case "*":
            calculate = new Multiplication();
            break;
    }

执行结果:

好了,就这么方便,一个新的功能就扩展完毕了,我根本不需要查看源代码是如何实现的,这就是多态的好处!

时间: 2024-10-14 02:23:11

C# 多态(虚方法,抽象,接口实现)的相关文章

多态—虚方法、抽象类、接口

修饰符:public : 公共的,引用命名空间即可随意访问,访问权限最高:private : 私有的,只有在声明它的类和结构中才可以访问,访问权限最低: Internal : 内部的,同一个程序集中所有的类都可以访问,程序集就是命名空间, 访问权限次最高,这个访问修饰符是默认的:Protected : 受保护的,只能在他自己和自己的子类中才能访问. --------------------------------------------------------------------------

C#中的抽象方法,虚方法,接口之间的对比

1.首先来看一看抽象类 抽象类是特殊的类,不能够被实例化:具有类的其他特性:抽象方法只能声明于抽象类中,且不包含任何实现 (就是不能有方法体),派生类也就是子类必须对其进行重写.另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们.关键字就是 abstract /* * 抽象方法必须写在,抽象类中 * */ public abstract class Animal { public abstract void Sleep(); public a

抽象类、虚方法、接口的区别

接口 1.接口只提供方法规约,不提供方法体: 2.接口中的方法不能用关键字修饰: 3.接口里不能有接口和变量: 4.接口里的方法在子类中必须全部实现: 5.接口可以实现多重继承: 抽象类 1.抽象类可以从接口继承: 2.抽象类中的实体方法在子类中不可以重写,只可以被引用: 3.抽象类中的抽象方法不可以有方法体,抽象类中的抽象方法在子类中必须重写: 4.抽象类中的虚方法在子类中可以选择性的重写: 虚方法 1.可以在子类选择性的重写: 2.不重写也可被子类调用: 接口与抽象类的异同 1.抽象类的抽象

Chapter 8. 面向对象(多态--虚方法)

namespace 多态 { public class Person { private string _name; public string Name { get { return _name; } set { _name = value; } } public Person(string name) { this.Name = name; } //虚方法 public virtual void SayHello() { Console.WriteLine("我是人类"); } }

PoEduo - C++阶段班【Po学校】-继承&amp;虚函数&amp;抽象&amp;接口- 课堂笔记

附录(一) 扩展知识:  1--> 面向对象的三个基本特征   1.0 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private,protected,public) 1.1 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力).可视继承(子窗体使用父窗体的外观和实现代码).接口继承(仅使用属性和方法,实现滞后到子类实现).前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式. 1.3 多态:是将

多态,虚方法重写,抽象类,接口

简而言之就是龙生九子,各有不同 有了继承,才有了多态 1.虚方法 virtual重写 override 父类中的方法,在子类中并不适用,那么子类需要自主更改继承的方法或者是属性,那父类中加了virtual关键字的方法才可以被子类重写 子类重写父类的方法使用的是override关键字 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 多态 { public

面向对象--多态、虚方法重写、抽象类、接口

多态: 一个父类有多个子类,每个子类都不尽相同 有了继承,才有了多态 虚方法:关键字 virtual 重写  override 父类中的方法在子类中并不适用,那么子类需要自主更改继承的方法或是属性, 父类中加了virtual关键字的方法才可以被子类重写,子类重写父类的方法使用的是override关键字 fly.cs中: namespace 多态 { public class fly { public virtual string fling() { return "拍翅膀飞"; } }

c# 多态实现_虚方法

实现方法: 虚方法, 抽象类, 接口 1.虚方法 将父类的方法标记为虚方法,使用关键字virtual,这个方法可以被子类重新写一遍. 在父类的方法前面加上一个virtual,在子类的方法前面加上一个override:如果子类的方法前面不加override,编译器不会报错,但这样的话,就无法通过父类来调用子类的方法,因为这个方法成了子类的独有的方法,只是名字与父类相同而已,与父类无关. 注意:如果需要对父类的方法进行重写的话,一定要在子类的方法面前加上overrie进行修饰. 什么时候使用:几个类

浅谈C# 多态的魅力(虚方法,抽象,接口实现)

浅谈C# 多态的魅力(虚方法,抽象,接口实现) 分类:             asp.net             C#              2014-02-08 11:29     786人阅读     评论(0)     收藏     举报 浅谈C# 多态的魅力(虚方法,抽象,接口实现) 前言:我们都知道面向对象的三大特性:封装,继承,多态.封装和继承对于初学者而言比较好理解,但要理解多态,尤其是深入理解,初学者往往存在有很多困惑,为什么这样就可以?有时候感觉很不可思议,由此,面向

《转》 浅谈C# 多态的魅力(虚方法,抽象,接口实现)

前言:我们都知道面向对象的三大特性:封装,继承,多态.封装和继承对于初学者而言比较好理解,但要理解多态,尤其是深入理解,初学者往往存在有很多困惑,为什么这样就可以?有时候感觉很不可思议,由此,面向对象的魅力体现了出来,那就是多态,多态用的好,可以提高程序的扩展性.常用的设计模式,比如简单工厂设计模式,核心就是多态. 其实多态就是:允许将子类类型的指针赋值给父类类型的指针.也就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果.在运行时,可以通过指向基类的指针,来调用实现派生类中的方