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();
}
这种算是面向接口开发。预留接口,进行后续扩展。
2.7.2多态概念回顾
在继承关系的前提下, 实例化出不同的对象, 这些对象调用相同的方法, 但是却
表现出不同的行为, 这就叫做多态。
C#面向对象(三):多态的更多相关文章
- Python_day8_面向对象(多态、成员修饰符、类中特殊方法、对象边缘知识)、异常处理之篇
一.面向对象之多态 1.多态:简而言子就是多种形态或多种类型 python中不支持多态也用不到多态,多态的概念是应用与java/C#中指定传参的数据类型, java多态传参:必须是传参数的数据类型或传 ...
- Python面向对象三要素-继承(Inheritance)
Python面向对象三要素-继承(Inheritance) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.继承概述 1>.基本概念 前面我们学习了Python的面向对象三 ...
- 「MoreThanJava」Day 6:面向对象进阶——多态
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
- 深入理解OOP(三):多态和继承(动态绑定和运行时多态)
在前面的文章中,我们介绍了编译期多态.params关键字.实例化.base关键字等.本节我们来关注另外一种多态:运行时多态, 运行时多态也叫迟绑定. 深入理解OOP(一):多态和继承(初期绑定和编译时 ...
- 黑马程序员_Java面向对象3_多态
5.面向对象_多态 多态定义:某一种事物存在的多种形态. 例:动物中猫,狗. 猫这个对象对应的类型是猫类型. 猫 x = new 猫(); 同时猫也是动物的一种,也可以把猫称为动物. 动物 y = n ...
- Python入门之面向对象的多态和继承
本章内容 Python面向对象的多态和继承对比 ========================================= 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的 ...
- Java面向对象之多态(来源于身边的案例)
2019年1月3日 星期四 Java面向对象之多态(来源于身边的案例) 1. 为什么要用多态? 1.1 多态是面向对象的三大特性之一 1.2 多态是基于接口设计的模型 1.3 多态具有横向扩展特性 1 ...
- go面向对象之多态即接口(interface)
Go 语言接口 Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口. 实例 /* 定义接口 */ type interface ...
- Python面向对象三要素-封装(Encapsulation)
Python面向对象三要素-封装(Encapsulation) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.封装概述 将数据和操作组织到类中,即属性和方法 将数据隐藏起来,给 ...
- Day 20: 面向对象【多态,封装,反射】字符串模块导入/内置attr /包装 /授权
面向对象,多态: 有时一个对象会有多种表现形式,比如网站页面有个按钮, 这个按钮的设计可以不一样(单选框.多选框.圆角的点击按钮.直角的点击按钮等),尽管长的不一样,但它们都有一个共同调用方式,就是o ...
随机推荐
- MySql安装成功后命令行进行必要的配置
1.1 首次用命令行登录 用zip方式安装成功mysql,并通过net start mysql 命令正常启动mysql服务后,打开dos命令行窗口,输入“mysql -uroot -p”或“mysql ...
- 教你在Yii2.0框架中如何创建自定义小部件
本教程将帮助您创建自己的自定义小部件在 yii framework 2.0.部件是可重用的模块和用于视图. 创建一个小部件,需要继承 yii\base\Widget,覆盖重写 yii\base\Wid ...
- PAT 天梯赛 L1-039. 古风排版 【字符串处理】
题目链接 https://www.patest.cn/contests/gplt/L1-039 思路 先根据 len 和 n 判断 有几个 列和几行,然后 从最右边 到 最左边 从上到下 将字符串 录 ...
- Java中ArrayList和LinkedList区别、ArrayList和Vector的区别
一般大家都知道ArrayList和LinkedList的大致区别: 1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构. 2.对于随机访问get和set,Ar ...
- PHP面向对象之对象和引用
在PHP中对象类型和简单变量类型表现可以说是大相径庭,很多数据类型都要可以在写时进行复制,如当写代码$a=$b时,两个变量因为赋予相同的值而告终.所以需要注意的是,这种情况用在对象时就会完全不同了. ...
- Python 集合set概念和操作
# 集合 # 概念 # 无序的, 不可随机访问的, 不可重复的元素集合 # 与数学中集合的概念类似,可对其进行交.并.差.补等逻辑运算 # 分为可变集合和非可变集合 # set # 为可变集合 # 增 ...
- Struts2全局异常处理
1.在struts.xml中配置全局异常处理 在Action中抛出异常,此异常可以是action自己抛的,也可以是Service抛出来的,都会跳转到全局异常中,只有在当前Action中配置域全局异常返 ...
- Struts2的Action中访问servletAPI方式
struts2的数据存放中心为ActionContext,其是每次请求来时都会创建一个ActionContext,访问结束销毁,其绑定在ThreadLocal上,由于每次访问web容器都会为每次请求创 ...
- [cqoi2012]交换棋子
2668: [cqoi2012]交换棋子 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 1334 Solved: 518[Submit][Stat ...
- Codeforces Round #275 (Div. 2) D
题意 : 一个数组 给出m个限制条件 l r z 代表从l 一直 & 到 r 为 z 问能否构造出这种数组 如果可以 构造出来 因为 n m 都是1e5 而l r 可能输入进去就超时了 所以 ...