C# 抽象类与接口的区别

前言

在 C# 中关于抽象类与接口到底有什么区别,在不同的应用场景中如何选择?这个问题困扰了我很久.前两天在做类图的时候好像突然想明白了,所以记录一下我对抽象类与接口的理解,供其他同学参考学习.

定义

关于它们的定义网上有太多太多了,这里不再赘述

简单总结

抽象类: 定义一个物体 它是什么
接口: 定义一个物体 能做什么

举例说明

本文会用两个例子来说明
1.一个简单的 绘制图形 的例子
2.一个 技能行为 的例子

例子1

某游戏策划需要我在游戏场景中画出一个圆形.这是一个十分简单的需求,代码如下

圆形类

using System;

namespace myApp
{
    public class Circle
    {
        public void Draw()
        {
            // TODO 绘制过程......
            Console.WriteLine("我画出了一个圆形");
        }
    }
}

调用类

namespace myApp
{
    public class Demo01
    {
        public void Main()
        {
            var c = new Circle();
            Draw(c);
        }
        
        private void Draw(Circle c)
        {
            c.Draw();
        }
    }
}

结果过了两天,策划一拍脑袋,他觉得圆形不好看,要改成画一个三角形.拿到需求的我稍加思索,把之前的代码稍微改了一下

形状的抽象类:

namespace myApp
{
    public abstract class Shape
    {
        // 这里可能还有很多其他属性,比如周长,颜色 等等, 这里就不举例说明了
        public int Id;

        // 不管什么形状, 我知道它都需要可以被绘制出来,所以一定要有 Draw 方法
        // 但是我在这里并不知道我具体要绘制哪个形状
        // 所以把它设置为抽象方法,让具体的子类去实现这个方法
        public abstract void Draw();
    }
}

三角形:

using System;

namespace myApp
{
    public class Triangle : Shape
    {
        public override void Draw()
        {
            Console.WriteLine("我画出了一个三角形");
        }
    }
}

调用类:

namespace myApp
{
    public class Demo01
    {
        public void Main()
        {
            
            var s = new Triangle();
            Draw(s);
            
            // 被抛弃的圆形
            // var c = new Circle();
            // Draw(c);
        }
        
        // 使用抽象类作为参数,我不需要关系它是圆形还是三角形
        // 我只需要知道它可以被绘制(拥有 draw 方法)
        private void Draw(Shape shape)
        {
            shape.Draw();
        }
    }
}

很好, 这样不管策划要画什么形状,我都不需要修改 Draw 方法了,只需要继承 Shape 类就可以被 Draw, 完美!

结果脑洞大开的策划觉得只画图形的话,表现形式太单调,现在他需要画一个 出来!

我:???

这可怎么办, 把 People 继承 Shape ? 这显然说不过去. 这时候接口的作用就体现出来了

定义 IDraw 接口

namespace myApp
{
    public interface IDraw
    {
        void Draw();
    }
}

实现了 IDraw 接口的 Shape 类(无改动)

namespace myApp
{
    public abstract class Shape : IDraw
    {
        // 这里可能还有很多其他属性,比如周长,颜色 等等, 这里就不举例说明了
        public int Id;

        // 不管什么形状, 我知道它都需要可以被绘制出来,所以一定要有 Draw 方法
        // 但是我在这里并不知道我具体要绘制哪个形状
        // 所以把它设置为抽象方法,让具体的子类去实现这个方法
        public abstract void Draw();
    }
}

people 类

using System;

namespace myApp
{
    public class People : IDraw
    {
        public void Draw()
        {
            Console.WriteLine("我画出了一个人");
        }
    }
}
namespace myApp
{
    public class Demo01
    {
        public void Main()
        {
            
            var s = new Triangle();
            Draw(s);
            
            var p = new People();
            Draw(p);
            
            // 被抛弃的圆形
            // var c = new Circle();
            // Draw(c);
        }
        
        // 使用接口作为参数
        // 我不需要知道它们是什么
        // 我只需要知道 不管它们是什么,都是可以被绘制的 (拥有 draw 方法)
        private void Draw(IDraw draw)
        {
            draw.Draw();
        }
    }
}

例子 2

以 LOL 中安妮的 Q 和 R的 技能行为 举例

Q: 创建投射物,命中后造成伤害
R: 创建一只熊,对目标区域造成伤害, 之后这只熊执行 熊的行为(熊的行为是什么不在当前讨论范围内)

简单分析

共同点: Q 和 R 都是主动技能, 都能造成伤害
不同点: Q 是创建投射物, R 是创建单位

所以: 现在需要两个类, 一个是创建投射物的类, 一个是创建单位(生物)的类

技能行为 (Skill Behavior)

什么是技能行为? 简单的说就是你释放技能后,技能的表现形式

抽象与实现

如何将这两个类的抽象成接口呢?
很简单,定义 ISkillBehavior 接口, 以及 DoBehavior 方法, 让这两个技能实现这个接口.
不管是创建投射物还是创建单位, 它们都需要 创建 出来, 至于该创建什么,该怎么创建就交给具体的类去实现. 在技能流程执行到 技能行为 的时候,只需要提供 DoBehavior 方法就行了,而不需要关心这个技能的实现是投射物还是其他什么东西

总结

抽象类: 提取相同事物相同的属性与方法
接口: 提取不同事物相同的属性与方法

如果是同一种事物完全可以使用抽象类,但很多时候我们并不知道之后需要做哪些事物. 就如同我在画圆形的时候不会想到今后需要画一个人出来.

所以,当遇到这种情况的时候,我们需要去提取出不同事物的相同点,并抽象成接口,而不是修改调用方法. 或新增很多 if else

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值