面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。
多态是同一个方法(行为)具有多个不同表现形式或形态的能力。 或者一个对象并不是实际声明的对象,而是它的子类构建出来的对象。
多态分为两种 静态多态 和 动态多态
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
在一个类当中,我们可以定义多个相同方法名的方法函数,方法里面的参数可以不同,方法的参数类型可以不同, 返回值也可以不同。 这样在我们写其它的方法调用它的时候,相当于这个方法就可以适应多种的参数形态了。 例子
public class Duck
{
public void Eat(int abc)
{
Console.WriteLine("I eat a int type; " + abc);
}
public int Eat(float abc)
{
Console.WriteLine("I eat a float type; " + abc);
return (int)abc;
}
public void Eat()
{
Console.WriteLine("I eat a nothing; ");
}
}
class Program
{
static async Task Main(string[] args)
{
var duck = new Duck();
duck.Eat(); // I eat a nothing;
duck.Eat(10); // I eat a int type; 10
duck.Eat(15f); //I eat a float type; 15
}
}
在运行时,派生类的对象可能被视为基类的对象。 当这种多态形为发生时,对象的声明类型不再与其运行时类型相同。 (在如下的代码中 我们声明了AbstractBird的list,但里面的实际对象是Bird和Duck,也就是说这个AbstractBird有了多种形态, IFly同理)
在C#中我们可以定义一个基类,抽象类,或者接口, 基类可以定义一个虚方法,抽象类可以定义一个抽象方法,派生类可以重写它们,这意味着这个方法它们有着自己的定义和实现。 在运行时,当客户端代码调用该方法时,CLR 会查找对象的运行时类型,并调用具体类型的方法。
如下。
// 定义了一个接口IFly
public interface IFly
{
void Fly();
}
//抽象类,继承于IFly接口。 (AbstractBird是可以不继承于IFly的。只是这样的话AbstractBird就转不成IFly类型了)
public abstract class AbstractBird : IFly
{
public abstract void Fly();
}
// 定义了一个Bird 实现了AbstractBird,
public class Bird : AbstractBird
{
//实现 fly方法
public override void Fly()
{
Console.WriteLine("我是一只鸟,我会飞。");
}
}
//继承于Bird 并重写了 Fly方法
public class Duck : Bird
{
//重写fly
public override void Fly()
{
Console.WriteLine("我是一只鸭,我会飞。");
}
}
// 只实现了IFly
public class Plane : IFly
{
// 实现Fly方法,并允许被重写 virtual
public virtual void Fly()
{
Console.WriteLine("我是一架飞机,我会飞。");
}
}
public class Copter : Plane
{
//重写fly
public override void Fly()
{
Console.WriteLine("我是一架直升机,我会飞。");
}
}
class Program
{
static async Task Main(string[] args)
{
var birds = new List<AbstractBird>() { new Bird(), new Duck() };
foreach (var bird in birds)
{
bird.Fly(); //同是fly方法,但是执行的东西是不一样的
}
//我是一只鸟,我会飞。
//我是一只鸭,我会飞。
var flyList = birds.Select(it => (IFly)it).ToList(); // 如果AbstractBird没有继承于IFly这边就没有办法转型了
flyList.Add(new Plane());
flyList.Add(new Copter());
foreach (var fly in flyList)
{
fly.Fly(); //同是fly方法,但是执行的东西是不一样的
}
//我是一只鸟,我会飞。
//我是一只鸭,我会飞。
//我是一架飞机,我会飞。
//我是一架直升机,我会飞。
}
}
比如说有时候我们需要从多个网站下载数据。 哪么我们可以定义一个 IDownloader的接口。里面有一个Download方法 然后我们我们的数据源有 新浪 东方财富 QQ等。哪我们就可以定义出 SinaDownloader, QQDownloader, EastMoneyDownloader 调用的地方就可以一次性全部调用了。
或者有时候我们做第三方的在线支付(假设第三方的返回形式都是Url),我们可以定义一个IPay. 代码如下。
public interface IPay
{
string GetPayUrl();
}
public class AliPay : IPay
{
public string GetPayUrl()
{
// some code
return "AliPay Url";
}
}
public class WeChatPay : IPay
{
public string GetPayUrl()
{
// some code
return "WeChatPay Url";
}
}
class Program
{
static async Task Main(string[] args)
{
IPay pay = CreatePayByUser();
var url = pay.GetPayUrl();
// 让页面跳转到这个支付Url
}
public static IPay CreatePayByUser()
{
//跟据用户的选择
//得到一些参数
//创建具体的Pay对象
return default;
}
}
如上面的实现,我们就可以把这个变化点放到具体的支付接口了。 新增加一个支付接口时。我们就新建一个支付类,然后稍微改一下 CreatePayByUser这个方法。 (实际上CreatePayByUser这个方法也是可以改成反射的方式来自动获得新加入的支付类) 这样就做到面象接口,而不是具体实现了。