兴义网站建设,wordpress 星星评分,windows优化大师官方免费,网站建设核心点一 C# 多态的实现
封装、继承、多态#xff0c;面向对象的三大特性#xff0c;前两项理解相对容易#xff0c;但要理解多态#xff0c;特别是深入的了解#xff0c;对于初学者而言可能就会有一定困难了。我一直认为学习OO的最好方法就是结合实践#xff0c;封装、继承在…一 C# 多态的实现
封装、继承、多态面向对象的三大特性前两项理解相对容易但要理解多态特别是深入的了解对于初学者而言可能就会有一定困难了。我一直认为学习OO的最好方法就是结合实践封装、继承在实际工作中的应用随处可见但多态呢也许未必可能不经意间用到也不会把它跟“多态”这个词对应起来。在此抛砖引玉大家讨论个人能力有限不足之处还请指正。 之前看到过类似的问题如果面试时主考官要求你用一句话来描述多态尽可能的精炼你会怎么回答当然答案有很多每个人的理解和表达不尽相同但我比较趋向这样描述通过继承实现的不同对象调用相同的方法表现出不同的行为称之为多态。
例1
代码
public class Animal{public virtual void Eat(){Console.WriteLine(Animal eat);}}public class Cat : Animal{public override void Eat(){Console.WriteLine(Cat eat);}}public class Dog : Animal{public override void Eat(){Console.WriteLine(Dog eat);}}class Tester{static void Main(string[] args){Animal[] animals new Animal[3];animals[0] new Animal();animals[1] new Cat();animals[2] new Dog();for (int i 0; i 3; i){animals[i].Eat();}}}
输出如下
Animal eat...
Cat eat...
Dog eat...
在上面的例子中通过继承使得Animal对象数组中的不同的对象在调用Eat()方法时表现出了不同的行为。
多态的实现看起来很简单要完全理解及灵活的运用c#的多态机制也不是一件容易的事有很多需要注意的地方。
1. new的用法
先看下面的例子。
例2
代码
public class Animal{public virtual void Eat(){Console.WriteLine(Animal eat);}}public class Cat : Animal{public new void Eat(){Console.WriteLine(Cat eat);}}class Tester{static void Main(string[] args){Animal a new Animal();a.Eat();Animal ac new Cat();ac.Eat();Cat c new Cat();c.Eat();}}
运行结果为
Animal eat...
Animal eat...
Cat eat...
可以看出当派生类Cat的Eat()方法使用new修饰时Cat的对象转换为Animal对象后调用的是Animal类中的Eat()方法。其实可以理解为使用new关键字后使得Cat中的Eat()方法和Animal中的Eat()方法成为毫不相关的两个方法只是它们的名字碰巧相同而已。所以 Animal类中的Eat()方法不管用还是不用virtual修饰也不管访问权限如何或者是没有都不会对Cat的Eat()方法产生什么影响只是因为使用了new关键字如果Cat类没用从Animal类继承Eat()方法编译器会输出警告。
我想这是设计者有意这么设计的因为有时候我们就是要达到这种效果。严格的说不能说通过使用new来实现多态只能说在某些特定的时候碰巧实现了多态的效果。
2.override实现多态
真正的多态使用override来实现的。回过去看前面的例1在基类Animal中将方法Eat()用virtual标记为虚拟方法再在派生类Cat和Dog中用override对Eat()修饰进行重写很简单就实现了多态。需要注意的是要对一个类中一个方法用override修饰该类必须从父类中继承了一个对应的用virtual修饰的虚拟方法否则编译器将报错。
好像讲得差不多了还有一个问题不知道你想没有。就是多层继承中又是怎样实现多态的。比如类A是基类有一个虚拟方法method()virtual修饰类B继承自类A并对method()进行重写override修饰现在类C又继承自类B是不是可以继续对method()进行重写并实现多态呢看下面的例子。
例3:
代码
public class Animal{public virtual void Eat(){Console.WriteLine(Animal eat);}}public class Dog : Animal{public override void Eat(){Console.WriteLine(Dog eat);}}public class WolfDog : Dog{public override void Eat(){Console.WriteLine(WolfDog eat);}}class Tester{static void Main(string[] args){Animal[] animals new Animal[3];animals[0] new Animal();animals[1] new Dog();animals[2] new WolfDog();for (int i 0; i 3; i){animals[i].Eat();}}
} 运行结果为
Animal eat...
Dog eat...
WolfDog eat...
在上面的例子中类Dog继承自类Animal对方法Eat()进行了重写类WolfDog又继承自Dog再一次对Eat()方法进行了重写并很好地实现了多态。不管继承了多少层都可以在子类中对父类中已经重写的方法继续进行重写即如果父类方法用override修饰如果子类继承了该方法也可以用override修饰多层继承中的多态就是这样实现的。要想终止这种重写只需重写方法时用sealed关键字进行修饰即可。
3. abstract-override实现多态
先在我们在来讨论一下用abstract修饰的抽象方法。抽象方法只是对方法进行了定义而没有实现如果一个类包含了抽象方法那么该类也必须用abstract声明为抽象类一个抽象类是不能被实例化的。对于类中的抽象方法可以再其派生类中用override进行重写如果不重写其派生类也要被声明为抽象类。看下面的例子。
例4
代码
public abstract class Animal{public abstract void Eat();}public class Cat : Animal{public override void Eat(){Console.WriteLine(Cat eat);}}public class Dog : Animal{public override void Eat(){Console.WriteLine(Dog eat);}}public class WolfDog : Dog{public override void Eat(){Console.WriteLine(Wolfdog eat);}}class Tester{static void Main(string[] args){Animal[] animals new Animal[3];animals[0] new Cat();animals[1] new Dog();animals[2] new WolfDog();for (int i 0; i animals.Length; i){animals[i].Eat();}}}
运行结果为
Cat eat...
Dog eat...
Wolfdog eat...
从上面可以看出通过使用abstract-override可以和virtual-override一样地实现多态包括多层继承也是一样的。不同之处在于包含虚拟方法的类可以被实例化而包含抽象方法的类不能被实例化。 二 浅谈C#多态的法力
前言我们都知道面向对象的三大特性封装继承多态。封装和继承对于初学者而言比较好理解但要理解多态尤其是深入理解初学者往往存在有很多困惑为什么这样就可以有时候感觉很不可思议由此面向对象的魅力体现了出来那就是多态多态用的好可以提高程序的扩展性。常用的设计模式比如简单工厂设计模式核心就是多态。
其实多态就是允许将子类类型的指针赋值给父类类型的指针。也就是同一操作作用于不同的对象可以有不同的解释产生不同的执行结果。在运行时可以通过指向基类的指针来调用实现派生类中的方法。如果这边不理解可以先放一放先看下面的事例看完之后再来理解这句话就很容易懂了。 理解多态之前首先要对面向对象的里氏替换原则和开放封闭原则有所了解。
里氏替换原则Liskov Substitution Principle派生类子类对象能够替换其基类超类对象被使用。通俗一点的理解就是“子类是父类”举个例子“男人是人人不一定是男人”当需要一个父类类型的对象的时候可以给一个子类类型的对象当需要一个子类类型对象的时候给一个父类类型对象是不可以的
开放封闭原则Open Closed Principle封装变化、降低耦合软件实体应该是可扩展而不可修改的。也就是说对扩展是开放的而对修改是封闭的。因此开放封闭原则主要体现在两个方面对扩展开放意味着有新的需求或变化时可以对现有代码进行扩展以适应新的情况。对修改封闭意味着类一旦设计完成就可以独立完成其工作而不要对类进行任何修改。
对这两个原则有一定了解之后就能更好的理解多态。
首先我们先来看下怎样用虚方法实现多态
我们都知道喜鹊Magpie、老鹰Eagle、企鹅Penguin都是属于鸟类我们可以根据这三者的共有特性提取出鸟类Bird做为父类喜鹊喜欢吃虫子老鹰喜欢吃肉企鹅喜欢吃鱼。
创建基类Bird如下添加一个虚方法Eat(): /// summary/// 鸟类父类/// /summarypublic class Bird{/// summary/// 吃虚方法/// /summarypublic virtual void Eat(){Console.WriteLine(我是一只小小鸟我喜欢吃虫子~);}}
创建子类Magpie如下继承父类Bird重写父类Bird中的虚方法Eat() /// summary/// 喜鹊子类/// /summarypublic class Magpie:Bird{/// summary/// 重写父类中Eat方法/// /summarypublic override void Eat(){Console.WriteLine(我是一只喜鹊我喜欢吃虫子~);}}
创建一个子类Eagle如下继承父类Bird重写父类Bird中的虚方法Eat() /// summary/// 老鹰子类/// /summarypublic class Eagle:Bird{/// summary/// 重写父类中Eat方法/// /summarypublic override void Eat(){Console.WriteLine(我是一只老鹰我喜欢吃肉~);}}
创建一个子类Penguin如下继承父类Bird重写父类Bird中的虚方法Eat() /// summary/// 企鹅子类/// /summarypublic class Penguin:Bird{/// summary/// 重写父类中Eat方法/// /summarypublic 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();}
运行结果 由此可见子类MagpieEaglePenguin对象可以赋值给父类对象也就是说父类类型指针可以指向子类类型对象这里体现了里氏替换原则。
父类对象调用自己的Eat()方法实际上显示的是父类类型指针指向的子类类型对象重写父类Eat后的方法。这就是多态。
多态的作用到底是什么呢 其实多态的作用就是把不同的子类对象都当作父类来看可以屏蔽不同子类对象之间的差异写出通用的代码做出通用的编程以适应需求的不断变化。 以上程序也体现了开放封闭原则如果后面的同事需要扩展我这个程序还想再添加一个猫头鹰Owl很容易只需要添加一个Owl类文件继承Bird重写Eat()方法添加给父类对象就可以了。至此该程序的扩展性得到了提升而又不需要查看源代码是如何实现的就可以扩展新功能。这就是多态带来的好处。
我们再来看下利用抽象如何来实现多态
还是刚才的例子我们发现Bird这个父类我们根本不需要使用它创建的对象它存在的意义就是供子类来继承。所以我们可以用抽象类来优化它。 我们把Bird父类改成抽象类Eat()方法改成抽象方法。代码如下 /// summary/// 鸟类基类/// /summarypublic abstract class Bird{/// summary/// 吃抽象方法/// /summarypublic abstract void Eat();}
抽象类Bird内添加一个Eat()抽象方法没有方法体。也不能实例化。 其他类MagpieEaglePenguin代码不变子类也是用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类作为基类StudentTeacher 类继承Person我们需要使用的是Student和Teacher创建的对象根本不需要使用Person创建的对象 所以在这里Person完全可以写成抽象类。
总而言之是使用虚方法或者抽象类抽象方法实现多态视情况而定什么情况以上我说的两点~
接下来~~~~
我要问一个问题喜鹊和老鹰都可以飞这个飞的能力我怎么来实现呢
XXX答“在父类Bird中添加一个Fly方法不就好了~~”
我再问“好的照你说的企鹅继承父类Bird但是不能企鹅不能飞啊这样在父类Bird中添加Fly方法是不是不合适呢”
XXX答“那就在能飞的鸟类中分别添加Fly方法不就可以了吗”
对这样是可以功能完全可以实现可是这样违背了面向对象开放封闭原则下次我要再扩展一个鸟类比如猫头鹰Owl我还要去源代码中看下Fly是怎么实现的然后在Owl中再次添加Fly方法相同的功能重复的代码这样是不合理的程序也不便于扩展
其次如果我还要添加一个飞机类Plane我继承Bird父类合适吗
很显然不合适所以我们需要一种规则那就是接口了喜鹊老鹰飞机我都实现这个接口那就可以飞了而企鹅我不实现这个接口它就不能飞~~
好接下来介绍一下接口如何实现多态~
添加一个接口IFlyable代码如下 /// summary/// 飞 接口/// /summarypublic interface IFlyable{void Fly();}
喜鹊Magpie实现IFlyable接口代码如下 /// summary/// 喜鹊子类实现IFlyable接口/// /summarypublic class Magpie:Bird,IFlyable{/// summary/// 重写父类Bird中Eat方法/// /summarypublic override void Eat(){Console.WriteLine(我是一只喜鹊我喜欢吃虫子~);}/// summary/// 实现 IFlyable接口方法/// /summarypublic void Fly(){Console.WriteLine(我是一只喜鹊我可以飞哦~~);}}
老鹰Eagle实现IFlyable接口代码如下 /// summary/// 老鹰子类实现飞接口/// /summarypublic class Eagle:Bird,IFlyable{/// summary/// 重写父类Bird中Eat方法/// /summarypublic override void Eat(){Console.WriteLine(我是一只老鹰我喜欢吃肉~);}/// summary/// 实现 IFlyable接口方法/// /summarypublic 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接口/// /summarypublic class Plane:IFlyable{/// summary/// 实现接口方法/// /summarypublic 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/// 计算父类/// /summarypublic abstract class Calculate{public int Number1{get;set;}public int Number2{get;set;}public abstract int Compute();}
接下来我们添加一个加法器继承计算Calculate父类 /// summary/// 加法器/// /summarypublic class Addition : Calculate{/// summary/// 实现父类计算方法/// /summary/// returns加法计算结果/returnspublic override int Compute(){return Number1 Number2;}}
再添加一个减法器继承计算Calculate父类 /// summary/// 减法器/// /summarypublic class Subtraction : Calculate{/// summary/// 实现父类计算方法/// /summary/// returns减法计算结果/returnspublic 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 nameoperation/param/// returns/returnsprivate Calculate GetCalculateResult(string operation){Calculate calculate null;switch (operation){case :calculate new Addition();break;case -:calculate new Subtraction();break;}return calculate;}
在该事件中主要调用GetCalculateResult方法通过运算符创建一个对应的加减乘除计算器子类然后赋值给父类其实这就是设计模式中的简单工厂设计模式我给你一个运算符你给我生产一个对应的加减乘除计算器子类返回给我。。其实大多数的设计模式的核心就是多态掌握好多态设计模式看起来也很轻松。
现阶段工作已经完成但是过了一段时间又添加新的需求了我还要扩展一个乘法了那好很简单只要创建一个乘法计算器继承Calculate父类即可看代码 /// summary/// 乘法计算器/// /summarypublic 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;}
执行结果 好了就这么方便一个新的功能就扩展完毕了我根本不需要查看源代码是如何实现的这就是多态的好处
多态性意味着有多重形式。在面向对象编程范式中多态性往往表现为一个接口多个功能。
多态性可以是静态的或动态的。在静态多态性中函数的响应是在编译时发生的。在动态多态性中函数的响应是在运行时发生的。 三 抽象和接口
什么时候用接口什么时候用抽象?
抽象:是对相同属性和方法的提炼而得
接口:是对相同行为不同实现方法的提炼
如: 每种支付方式 支付之前都需要校验一下支付金额是不是真确的不能小于等于0 。因为校验方式校验代码都是一样的所以我们可以定义一个 抽象类给抽象出来.
public abstract class AbstractPayWay implements PayWay{private Double money;private boolean verify(){return money ! null money 0; }/*** 这里实现了 PayWay 中的 pay 接口 方法 所以 AbstractPayWay 的子类 无需 实现该 方法* 只需要 实现 doPay 方法并且 如果 doPay方法被成功调用则说明肯定 校验成功了。*/Overridepublic boolean pay(){boolean verify this.verify();if(!verify){System.out.println(支付金额验证错误);return false;}return this.doPay();}public abstract boolean doPay();
}
所以 WeixinPayWay ZhifubaoPayWay 支付的具体实现类可以改改为
/*** 继承 AbstractPayWay 所以无需 写公共的校验逻辑直接写支付业务逻辑* author cyy*/
public class WeixinPayWay extends AbstractPayWay{Overridepublic boolean doPay() {System.out.println(这里无需校验支付金额直接调用支付方法就行);System.out.println(微信支付成功);return false;}}public class ZhifubaoPayWay extends AbstractPayWay{Overridepublic boolean doPay() {System.out.println(这里无需校验支付金额直接调用支付方法就行);System.out.println(支付宝支付成功);return false;}} 参考:
https://www.cnblogs.com/wl-blog/p/10361894.html
https://blog.csdn.net/wab719591157/article/details/73741919
https://blog.csdn.net/u012135077/article/details/48286837
https://blog.csdn.net/u013538542/article/details/45365019