Интерфейс vs абстрактный класс
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy. Урок 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: Определите интерфейс с именемIPlayableв пространстве имёнMedia. Этот интерфейс задаёт контракт для всего, что может воспроизводить медиафайлы, — будь то телефон, телевизор или специальный музыкальный плеер. В нём следует объявить один методPlay(), который возвращает строку.MediaFile.cs: Определите абстрактный класс с именемMediaFileв пространстве имёнMedia. Он представляет общую основу для всех медиафайлов. В нём должны быть свойствоTitle(строка) и свойствоDuration(целое число, обозначающее длительность в секундах). Добавьте конструктор, который устанавливает оба значения. Добавьте конкретный метод с именемGetInfo(), который возвращает"{Title} ({Duration}s)". Также объявите абстрактный методGetType(), который возвращает строку, — каждый тип медиафайла будет определять, что это за файл.AudioFile.cs: Определите классAudioFileв пространстве имёнMedia, который наследуется от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:, затем результат Play() для переменной типа IPlayable
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методовПотренируйтесь самостоятельно: Онлайн-компилятор C#