接口与抽象类
本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 26 课,共 70 课。
接口和抽象类都可以定义其他类必须遵守的契约,但它们的用途不同,功能也各有特点。
接口定义了一个类必须执行的操作,但不包含任何实现。它是一份纯粹的契约:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}抽象类既可以提供契约,也可以提供共享实现。它是一份不完整的蓝图:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // 必须实现
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}主要区别在于继承规则和用途。
一个类可以实现多个接口,但只能继承一个抽象类。
一个类也可以同时继承抽象类并实现接口:冒号后先写基类,再写接口,彼此用逗号分隔:
public class Airplane : Vehicle, IFlyable
{
public override void Drive()
{
Console.WriteLine("Taxiing to the runway");
}
public void Fly()
{
Console.WriteLine("Flying above the clouds");
}
}当不相关的类需要共享行为时,使用接口(例如,鸟类和飞机都可以使用 IFlyable)。当相关的类共享通用代码和状态时,使用抽象类(例如,车辆共享启动引擎的机制)。
抽象类可以有构造函数、字段和成员访问修饰符。传统上,接口不可以这样做,不过现代 C# 允许提供默认实现。
如果不确定,优先选择接口以获得灵活性。以后如果需要共享实现,你随时可以再添加一个抽象类。
挑战
简单让我们构建一个媒体播放器系统,展示何时使用接口,何时使用抽象类。你将设计这样一种结构:不相关的设备通过接口共享播放行为,而相关的媒体类型则通过抽象类共享通用实现。
你将把代码分到四个文件中:
IPlayable.cs:在Media命名空间中定义一个名为IPlayable的接口。此接口用于表示任何可以播放媒体的对象所遵循的约定,无论它是手机、电视还是专用音乐播放器。它应声明一个名为Play()的方法,该方法返回一个字符串。MediaFile.cs:在Media命名空间中定义一个名为MediaFile的抽象类。它是所有媒体文件的共同基础。它应包含一个Title属性(字符串)和一个Duration属性(整数,表示秒数)。添加一个用于设置这两个值的构造函数。添加一个名为GetInfo()的具体方法,返回"{Title} ({Duration}s)"。还要声明一个名为GetType()的抽象方法,该方法返回一个字符串,由每种媒体类型定义自己的文件类型。AudioFile.cs:在Media命名空间中定义一个AudioFile类,让它继承MediaFile并实现IPlayable。添加一个Artist属性(字符串)。构造函数应接收标题、时长和艺术家。重写GetType(),使其返回"Audio"。实现Play(),使其返回"Playing audio: {Title} by {Artist}"。Program.cs:在主文件中,使用输入值创建一个AudioFile。展示这个类的两个方面:调用GetInfo()和GetType()(从抽象类继承而来),然后调用Play()(来自接口)。将音频文件存储在一个IPlayable变量中,再次调用Play(),以展示接口多态。
你将收到三个输入:
- 歌曲名称
- 时长(秒)
- 艺术家姓名
按以下格式打印输出:
Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}例如,如果输入是 Bohemian Rhapsody、354 和 Queen,输出应为:
Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by Queen注意,AudioFile 同时受益于这两种方式:它从抽象类继承了通用实现(GetInfo()),同时也满足了 IPlayable 约定。抽象类为相关媒体类型提供代码复用,而接口则让任何不相关的类无需共享继承层次结构也能成为“可播放”的对象。
亲手试试
using System;
using Media;
class Program
{
public static void Main(string[] args)
{
// 读取输入值
string title = Console.ReadLine();
int duration = Convert.ToInt32(Console.ReadLine());
string artist = Console.ReadLine();
// TODO: 使用输入值创建一个 AudioFile
// TODO: 先输出 Info:,再输出 GetInfo() 的结果
// TODO: 先输出 Type:,再输出 GetType() 的结果
// TODO: 先输出 Direct:,再输出 Play() 的结果
// TODO: 将音频文件存入一个 IPlayable 变量
// TODO: 先输出 Via Interface:,再输出 IPlayable 变量的 Play() 结果
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C# 在线编译器