C#有关虚函数、抽象类和接口

在面向对象编程中,虚函数、抽象类和接口都是用于实现多态性的关键概念,它们各自有着独特的用途和语义。

以下的代码举例用RPG游戏中的怪物与玩家举例。

虚函数

虚函数(关键字virtual)是在基类(父类)中声明的函数,可以在派生类(子类)中进行重写。这提供了运行时多态性,意味着调用哪个方法取决于对象的实际类型,而不仅仅是变量的类型。虚函数通常在基类中提供一个默认实现,但派生类可以使用Override关键字来提供一个不同的实现。

定义一个怪物(敌人)基类,怪物可以攻击玩家,再定义一个史莱姆,史莱姆作为怪物自然可以攻击玩家。我们希望输出的是“某某怪物攻击了玩家“而不是“怪物攻击了玩家”,因此对Attack方法进行重写。

    class Enemy
    {
        int HP;
        int ATK;
        float moveSpeed;
        public virtual void Attack()
        {
            Console.WriteLine("怪物攻击了玩家!");
        }
    }

    class Slime:Enemy
    {
        public override void Attack()
        {
            base.Attack();
            Console.WriteLine("史莱姆攻击了玩家!");
        }
    }

    class Program
    {
        static void Main()
        {
            Slime slime = new Slime();
            slime.Attack();
        }
    }

 最后输出的是两段话,原因是base.Attack();它的作用是引用了Enemy类中的Attack方法的实例,如果我们不需要可以将其注释或删除。

抽象类

抽象类(abstract )是指未被成员函数实现的类,不能被实例化只能被继承,因此抽象类专为基类而生。

如上例,我们并不需要输出“怪物攻击了玩家”,换句话说我们并不需要Enemy类中的Attack方法,因为每个怪物的Attack是不同的,此时可以将其改成抽象类。

    abstract class Enemy //改成抽象类时 对应方法也要加上关键字
    {
        int HP;
        int ATK;
        float moveSpeed;
        public abstract void Attack();
    }

    class Slime : Enemy
    {
        public override void Attack()
        {
            //base.Attack(); //会报错 原因是无法调用抽象基成员
            Console.WriteLine("史莱姆攻击了玩家!");
        }
    }

 这样就输出“史莱姆攻击了玩家!”这一句话了。

接口

接口(interface)作为另一种实现多态性的手段,定义了一组方法、属性、事件和索引器的签名集合,但不包含任何实现细节。任何实现接口的类都必须提供接口中所有成员的具体实现,这使得接口成为了一种严格的契约,确保了实现该接口的类具备一致的公共行为,而不必关心其内部实现。接口内方法默认public。

我们拓写怪物的行为,除了攻击还可以受伤和死亡,将其封装成接口,接口通常用I开头,方便区分和管理。

    interface IEnemy
    {
        void Attack();
        void Damage();
        void Dead();
    }

    abstract class Enemy : IEnemy
    {
        int HP;
        int ATK;
        float moveSpeed;
        public abstract void Attack();//接口需要对所有方法进行定义 因此此时可以复用之前的Attack方法使用抽象类

        public void Damage()
        {
            Console.WriteLine("敌人受伤!");
        }

        public void Dead()
        {
            Console.WriteLine("敌人死亡!");
        }
    }

    class Slime : Enemy
    {
        public override void Attack()
        {
            //base.Attack();
            Console.WriteLine("史莱姆攻击了玩家!");
        }
    }

    class Program
    {
        static void Main()
        {
            Slime slime = new Slime();
            slime.Attack();
            slime.Damage();
            slime.Dead();
        }
    }

有关“接口隔离原则”

接口隔离原则(Interface Segregation Principle,ISP)是面向对象设计原则中的一条。其核心思想是“客户端不应该依赖它不需要的接口”,目的是解决接口臃肿和不一致的问题。具体实现方法是将一个大的接口拆分成多个小的接口,每个小的接口只包含一个独立的功能。

假设我们要添加一个新的怪物,取名为“黄金史莱姆”,它的作用是可以对周围的怪物进行治疗,但不能攻击玩家。但它不能写在怪物基类里,因为怪物并不会治疗,这是黄金史莱姆的特有技能。黄金史莱姆不能攻击玩家,因此也不能直接继承怪物基类,所以需要对IEnemy接口里的方法拆成各个小接口,只关注各个接口实现即可。

    interface IAttacker
    {
        void Attack();
    }

    interface IDamageable
    {
        void Damage();
    }

    interface IKillable
    {
        void Dead();
    }

    interface IHealer
    {
        void Heal();
    }

    class GoldenSlime : IDamageable, IKillable, IHealer
    {
        public void Damage()
        {
            Console.WriteLine("黄金史莱姆受伤!");
        }

        public void Dead()
        {
            Console.WriteLine("黄金史莱姆死亡!");
        }

        public void Heal()
        {
            Console.WriteLine("黄金史莱姆治疗了敌人!");
        }
    }

 虽然从代码量上看着有点长,但从设计角度而言这是值得的。

三者联系

先说结论——“特别虚就是抽象,特别抽象就是接口”

“特别虚就是抽象”

虚方法在达到某种“极端”的情况下,其实质上转变为了抽象方法,进而形成了抽象类。当一个类中包含了一个或多个只有声明没有实现的虚方法时,这个类就变成了抽象类,因为这样的类无法被实例化,必须由派生类提供具体实现。

像上文探讨的是基类中Attack方法对于派生类并不需要,所以改成抽象类。实际上基类的Attack方法不包含任何实现细节,即方法体为空,相当于base.Attack调用为空操作,也能实现效果。因此虚方法内容为空时可以理解为抽象(不是划等号)

    class Enemy //虚方法实现 派生类可以使用base关键字
    {
        public virtual void Attack()
        {

        }
    }

    abstract class Enemy  //抽象类实现 派生类不能使用base关键字
    {
        public abstract void Attack();
    }

“特别抽象就是接口”

如果抽象类中的所有方法都是抽象的,没有具体的实现,那么这个类实际上起到了接口的作用。

当一个抽象类中没有具体实现,只包含抽象方法时,它在功能上类似于接口,要求派生类必须提供所有抽象方法的实现。不过,从严格意义上讲,抽象类和接口在语言特性和使用场景上仍存在区别,比如抽象类可以包含非抽象成员,而接口不可以;一个类只能继承一个抽象类,但可以实现多个接口等。

    abstract class Enemy //抽象实现Enemy类
    {
        public abstract void Attack();
        public abstract void Damage();
        public abstract void Dead();
    }

    interface IEnemy //接口实现Enemy类
    {
        void Attack();
        void Damage();
        void Dead();
    }

抽象类侧重于描述一类事物的共性特征以及部分未知的具体实现,它可以有非抽象成员(即有具体实现的方法和属性),并且可以有多个层次的继承关系。
虚函数是为了实现多态,允许派生类改变或扩充基类中函数的行为。
接口则更加纯粹,只关注一组行为的规范而不关心内部实现细节,一个类可以实现多个接口,因此接口常用来表示“has-a”某种能力或者角色,而不是“is-a”某种类型的关系。 

 

如有不对,欢迎指出,感谢观看!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值