C# 多态

面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。

多态是同一个方法(行为)具有多个不同表现形式或形态的能力。 或者一个对象并不是实际声明的对象,而是它的子类构建出来的对象。

多态分为两种 静态多态 和 动态多态

静态多态性

在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。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这个方法也是可以改成反射的方式来自动获得新加入的支付类) 这样就做到面象接口,而不是具体实现了。

上一篇:C# 继承
最近更新的
...