zoukankan      html  css  js  c++  java
  • C#面向对象(三):多态

    前文链接:

    C#面向对象(一):明确几个简单的概念作为开胃菜

    C#面向对象(二):封装和继承

    今天来聊聊面向对象的多态,这部分算是比较重要和核心的,很多工作2年多的程序员其实对于面向对象和多态的理解也是不到位的,这次好好总结下,理顺思路。

    三、多态:

      有多态之前必须要有继承,只有多个类同时继承了同一个类,才有多态这样的说法。

      在继承关系的前提下,实例化出不同的对象,这些对象调用相同的方法,但是却表现出不同的行为,这就叫做多态。

      在 C#语言中体现多态有三种方式:虚方法,抽象类, 接口

    1、虚方法

    1.1什么是虚方法?

    在父类中使用 virtual 关键字修饰的方法, 就是虚方法。在子类中可以使用 override 关键字对该虚方法进行重写。

    Virtual方法也可以单独执行。

    1.2虚方法语法

    父类:

    public virtual 返回值类型 方法名()

    {

    方法体代码;

    }

    子类:

    public override 返回值类型 方法名()

    {

    方法体代码;

    }

    例:老虎和猫继承同一个父类,实现“ 叫”虚方法

        class CatType
        {
            public virtual void Cry()
            {
                Console.WriteLine("深呼吸,张开嘴巴,开始:");
            }
        }

    class Cat:CatType { public override void Cry() { base.Cry(); Console.WriteLine("喵喵喵"); } }

    class Tiger:CatType { public override void Cry() { base.Cry(); Console.WriteLine("咆哮"); } }

    1.3.虚方法使用细节

    ①将父类的方法标记为虚方法, 就是在父类方法的返回值前加 virtual 关键字,表示这个方法可以被子类重写。

    ②子类重写父类方法, 在子类的方法的返回值前加 override 关键字。

    ③父类中的虚方法, 子类可以重写, 也可以不重写。

    ④父类中用 virtual 修饰的方法, 可以用于实现该方法共有的功能(比如初始化该方法), 然后在子类重写该方法时, 使用 base 关键字调用父类中的该方法。

    2、多态之里氏转换原则

    2.1 面向对象六大原则

    在使用面向对象思想进行程序设计开发过程中, 有六大原则需要注意, 六大原则在面向对象编程中的地位类似于“ 马列主义” “ 毛XX思想” “ 邓小平理论” 等,作为编程的“ 指导思想” 和“ 行动指南” 存在的。

    六大原则如下:

    ①单一职责原则; ②开闭原则; ③里氏转换原则;

    ④依赖倒置原则; ⑤接口隔离原则; ⑥迪米特原则;

    这六大面向对象编程原则, 在后续中我们会一一介绍到, 本节课讲解第一个原则: 里氏转换原则!先定义一个子类,重写cry方法,增加monkey方法

        class Cat:CatType
        {
            public override void Cry()
            {
                base.Cry();
                Console.WriteLine("喵喵喵");
            }
            public void Monkey()
            {
                Console.WriteLine("我是子类--Cat类");
            }
        }

    2.2 何为里氏转换

    ①子类对象可以直接赋值给父类变量;

    而且父类的变量cry方法也被重写了(override)。

    ②子类对象可以调用父类中的成员, 但是父类对象永远只能调用自己的成员;

    CatType无法调用monkey();

    ③如果父类对象中装的是子类对象, 可以将这个父类对象强转为子类对象;

                //现在方式.
                CatType ct = new Cat();
                ct.Cry();
                ct.MKCODE();

    目前ct虽然是子类对象,但是装在父类中,所以无法调用子类的monkey方法,强制转化之后,就可以使用monkey方法了

                Cat c2 = (Cat)ct;

                c2.Monkey();

    这里我们用的是强制类型转换,也可以使用is 和 as 转换

    is 和 as 两个关键字都可以进行类型转换。

    is: 如果转换成功, 返回 true, 失败返回 false;

    as: 如果转换成功, 返回对应的对象, 失败返回 null。

                bool mk = ct is Tiger;
                Console.WriteLine(mk);
                if(ct as Cat == null)
                {
                    Console.WriteLine("转换失败");
                }else{
                    Console.WriteLine("转换成功");
                }

    2.3 多态之抽象类语法

    2.3.1 抽象方法

    虚方法到抽象方法

    父类里面用 virtual 关键字修饰的方法叫做虚方法,子类可以使用override重新该虚方法,也可以不重写。

    虚方法还是有方法体的,当我们父类中的这个方法已经虚到完全无法确定方法体的时候,就可以使用另外一种形式来表现,这种形式叫抽象方法。

    2.3.2抽象方法语法

    抽象方法的返回值类型前用关键字abstract修饰且无方法体

    public abstract void Hello();

    抽象方法必须存在于抽象类中

    abstract class FuLei

    在定义类的关键字class前面加abstract 修饰的类就是抽象类。

    子类继承抽象类,使用 override关键字重写父类中所有的抽象方法。

    2.3.3 抽象类注意事项

    <1>抽象类中不一定要有抽象方法, 但是抽象方法必须存在于抽象类中。

    <2>抽象类不能被实例化, 因为抽象类中有抽象方法(无方法体), 如果真能实例化抽象类的话, 调用这些无方法体的方法是没有任何意义的, 所以无法实例化。

    2.3.4 使用场景

    <1>当父类中的方法不知道如何去实现的时候, 可以考虑将父类写成抽象类,将方法写成抽象方法。

    <2>如果父类中的方法有默认实现, 并且父类需要被实例化, 这时可以考虑将父类定义成一个普通类, 用虚方法实现多态。

    <3>如果父类中的方法没有默认实现, 父类也不需要被实例化, 则可以将该类定义为抽象类。

    2.3.5 抽象类编程案例

    前置回顾

    <1>关于多态的实现方式已经介绍了虚方法,抽象类两种方式了。

    <2>多态的使用前提,是建立在继承的关系之上的,也就是说必须要先有继承关系,然后才会出现多态

    <3>面向对象的封装,继承,多态,都是我们后期规划代码结构的基本思想。

    <4>大点的项目可能会有几百个独立的脚本文件,这么多的脚本文件,如果没有一个代码结构框架来管理的话,项目十有八九是会中途夭折的。

    案例:使用抽象类结构实现NPC模块

    在游戏中会出现很多种不同用途的NPC,这些NPC有各自的存在的价值和作用,同时又具备一些共性的东西。在开发NPC系统的时候,往往是需要提取共性,独立出一个父类,然后子类继承实现不同作用的NPC。

    分析:

    任务 NPC, 商贩 NPC, 铁匠 NPC, 三种 NPC 的种类。

    共有属性: npc 的名字, npc 的类型;

    共有方法: 都能和玩家交互(交谈);

        abstract class NPC
        {
            private string name;
            private NPCType type;
            public string Name
            {
                get { return name; }
                set { name = value; }
            }
            public NPCType Type
            {
                get { return type; }
                set { type = value; }
            }
            public NPC(string name, NPCType type)
            {
                this.name = name;
                this.type = type;
            }
            public abstract void Speak();
        }
        class TaskNPC:NPC
        {
            private string taskInfo;
            public TaskNPC(string taskInfo, string name, NPCType type)
                : base(name, type)
            {//使用base,将name和type传递给父类,进行构造
                this.taskInfo = taskInfo;
            }
            public override void Speak()
            {
                Console.WriteLine("NPC{0},任务{1}", base.Name, taskInfo);
            }
        }

    2.3.6虚方法抽象类语法对比

    2.4 多态之接口语法

    2.4.1 接口语法

    抽象类到接口

    当抽象类中所有的方法都是抽象方法的时候,这个时候可以把这个抽象类用另外

    一种形式来表现,这种形式叫接口。

    虚方法,抽象类,接口是三种实现多态的手段。

    语法格式要求:

    接口使用 interface 关键字定义,没有 class 关键字,接口名一般使用 “IXxxx”

    (实际使用要在interface前加public ,因为我有时候为了依赖注入,直接使用接口来装载子类对象)

    这种方式进行书写, 在一堆脚本中通过名字判断, I 开头的都是接口。

    接口中不能包含字段,但是可以包含属性(? 没有字段,如何写属性那? ?使用自动属性 public int Age {get;set;})

    (公共字段只是类用public修饰符所公开的简单公共变量,而属性则是对字段的封装,它使用get和set访问器来控制如何设置或返回字段值。)

    接口中定义的方法不能有方法体,全是抽象方法,但又不需要用 abstract 修饰;

    接口中的成员不允许添加访问修饰符,默认都是 public

    (既然是接口里面的方法,当然需要从外面调用,必然是public了。)

    namespace xxx
    {
        interface IFly

         //实际的使用情况是,interface前面也有可能加public,里面的方法倒是不用加public。比如用接口的实例装载子类型对象

        {
            //接口中不能包含字段.
    
            //private string name;
    
            //接口中的方法不能有方法体,不能有访问修饰符(默认是public)
    
              void Fly();
        }
    }

    2.4.2 接口注意事项

    <1>接口中所有的方法都是抽象方法,所以接口不能被实例化;

    <2>一个类可以实现多个接口,被实现的多个接口之间用逗号分隔开;

    class Batmobile:Car,IFly

    <3>一个接口可以继承多个接口, 接口之间也要用逗号分隔。

    类与类之间只能单继承。

    使用场景:

    接口是一种能力,是一种规范,当我们对现在已经存在的类的继承关系进行功能扩展的时候,就可以使用接口来完成相应的工作。

    具有特殊功能属性或者方法的子类,使用接口完成他的特殊点。

    接口独立于原有的继承关系之外

    2.5 多态之接口案例

    2.5.1 C#属性

    常规属性:先定义一个私有的字段,然后在为这个私有字段封装一个公开的属性,在属性中实现get和set两个方法,这种方式叫做常规属性。

      当我们使用常规属性的时候,可以在get和set方法中,编写逻辑代码对取值和赋值进行逻辑的校验。这种方式是我们之前一直在使用的方式。

      自动属性:在某些情况下,属性的get和set只是完成字段的取值和赋值操作,而不包含任何附加的逻辑代码,这个时候可以使用自动属性。

    例如:

    public int Age {get;set;}

    不用写字段,直接写属性

    当我们使用自动属性的时候, 就不需要再写对应的字段了, C#编译器会自动给我们的自动属性提供一个对应的字段。

    注意:在接口中使用属性,就要写自动属性的格式,因为接口中不支持字段。

    2.5.2 接口案例

    模拟电脑USB接口

    所有的电脑上都有 USB 接口,这些USB接口存在的目的是为了方便对电脑进行功能上的扩展,可以在这些接口上插U盘,移动硬盘,手机,外置光驱等等。之所以可以在USB接口上插入这些外置设备,是因为这些设备的接口都符合USB 接口的协议,符合了这个协议,才能使设备可以正常的和电脑连接。

    编码实现:

    USB是一个接口。

    U盘,移动硬盘,手机是具体的产品,这些产品在满足了自身功能的前提后,还需要实现这个USB接口规定的功能。

         interface IUSB

        {

            void Read();

            void Write();

        }

    2.6 多态之虚方法抽象类接口对比

    2.6.1 语法格式对比

    综合对比虚方法, 抽象类, 接口 三者的语法格式, 以及相关的关键字。

    记牢语法格式!

    2.6.2使用场景对比

    虚方法:父类中的个别方法用虚方法实现,然后允许子类在有需要的情况下重写这些虚方法。

    virtual和override

    父类中包含虚方法也可以实例化对象。

    抽象类:父类定义一系列的规范,子类去把父类里面定义的这些规范全部实现。

    Abstract和override

    父类是抽象类,那么不能单独实例化。

    接口:是一种功能的扩展,是在原有的类的继承关系以外的新功能的扩展。

    Interface Ixxxx
    void B1();
        class Zi:Fu,IBBB
        {
            public void B1()
            {
                Console.WriteLine("B1");
            }
        }

    2.7多态之里氏转换原则案例

    2.7.1多态综合案例

    模拟电脑与外部移动设备的关系:

    创建三个类:电脑类,U盘类,移动硬盘类。

    模拟外部存储设备插入电脑后,电脑对二者的存取操作。

     

        class Computer
        {
            private string brand;
            public IUSB USB_1;
            public IUSB USB_2;
            public string Brand
            {
                get { return brand; }
                set { brand = value; }
            }
            public Computer(string brand)
            {
                this.brand = brand;
            }
            public void Start()
            {
                Console.WriteLine("{0}品牌的电脑开机中...", brand);
            }
            public void End()
            {
                Console.WriteLine("{0}品牌的电脑关机中...", brand);
            }
        interface IUSB
        {
            /// <summary>
            /// 读取移动设备中的数据.
            /// </summary>
            void Read();
            /// <summary>
            /// 往移动设备中写入数据.
            /// </summary>
            void Write(string content);
        }
    class HardDisk:Disk,IUSB
        {
            /// <summary>
            /// 硬盘的存储空间.
            /// </summary>
            private string content;
            public HardDisk(string brand)
                : base(brand)
            {
            }
            public void Read()
            {
                Console.WriteLine("{0}读取数据{1}", Brand, content);
            }
            public void Write(string content)
            {
                this.content += content;
                Console.WriteLine("{0}存入数据{1}", Brand, content);
            }
    }
    static void Main(string[] args)
            {
                UDisk u1 = new UDisk("金士顿32GB");
                HardDisk h1 = new HardDisk("三星500GB");
                Computer c1 = new Computer("联想");
                c1.Start();
                c1.USB_1 = u1;
                c1.USB_1.Write("擅码网");
                c1.USB_1.Write("MKCODE");
                c1.USB_1.Read();
                c1.USB_2 = h1;
                c1.USB_2.Write("mkcode.net");
                c1.USB_2.Write("lkk");
                c1.USB_2.Read();
                c1.End();
                Console.WriteLine();
                Computer c2 = new Computer("戴尔");
                c2.Start();
                c2.End();
                Console.ReadKey();
            }
    View Code

    这种算是面向接口开发。预留接口,进行后续扩展。

    2.7.2多态概念回顾

    在继承关系的前提下, 实例化出不同的对象, 这些对象调用相同的方法, 但是却

    表现出不同的行为, 这就叫做多态。

     

  • 相关阅读:
    leetcode33. Search in Rotated Sorted Array
    pycharm 设置sublime text3 monokai主题
    django class Meta
    leetcode30, Substring With Concatenation Of All Words
    Sublime text3修改tab键为缩进为四个空格,
    sublime text3 python打开图像的问题
    安装上imesupport输入法依然不跟随的解决办法,
    sublime text3 的插件冲突弃用问题,
    sublime text3 BracketHighlighter括号匹配的设置
    windows 下wget的使用
  • 原文地址:https://www.cnblogs.com/qixinbo/p/8244583.html
Copyright © 2011-2022 走看看