前言
在 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