Интерфейс 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");
}
}Основные различия сводятся к правилам наследования и назначению.
Класс может реализовывать несколько интерфейсов, но наследоваться только от одного абстрактного класса.
Используйте интерфейс, когда несвязанным классам необходимо общее поведение (например, IFlyable для птиц и самолетов). Используйте абстрактный класс, когда связанные классы имеют общий код и состояние (например, транспортные средства, имеющие общий механизм запуска двигателя).
Абстрактные классы могут иметь конструкторы, поля и модификаторы доступа для своих членов. Интерфейсы традиционно не могли этого делать, хотя современный C# допускает реализации по умолчанию.
Если сомневаетесь, отдавайте предпочтение интерфейсам для обеспечения гибкости — вы всегда сможете добавить абстрактный класс позже, если возникнет необходимость в общей реализации.
Задание
ЛегкоДавайте создадим систему медиаплеера, которая демонстрирует, когда использовать интерфейсы, а когда — абстрактные классы. Вы создадите архитектуру, в которой несвязанные устройства разделяют поведение воспроизведения через интерфейс, в то время как связанные типы медиафайлов разделяют общую реализацию через абстрактный класс.
Вы организуете свой код в четырех файлах:
IPlayable.cs: Определите интерфейс с именемIPlayableв пространстве именMedia. Этот интерфейс представляет собой контракт для всего, что может воспроизводить медиа — будь то телефон, телевизор или специализированный музыкальный плеер. Он должен объявлять один методPlay(), который возвращает строку.MediaFile.cs: Определите абстрактный класс с именемMediaFileв пространстве именMedia. Он представляет собой общую основу для всех медиафайлов. У него должно быть свойствоTitle(string) и свойствоDuration(int, представляющее секунды). Включите конструктор, который устанавливает оба значения. Добавьте конкретный метод с именемGetInfo(), который возвращает"{Title} ({Duration}s)". Также объявите абстрактный методGetType(), который возвращает строку — каждый тип медиа будет определять, что это за файл.AudioFile.cs: Определите классAudioFileв пространстве именMedia, который наследуется отMediaFileи реализуетIPlayable. Добавьте свойствоArtist(string). Конструктор должен принимать название, длительность и исполнителя. Переопределите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. Абстрактный класс обеспечивает повторное использование кода для связанных типов медиа, в то время как интерфейс позволяет любому несвязанному классу быть «воспроизводимым» без необходимости участия в общей иерархии наследования.
Шпаргалка
Интерфейс определяет контракт, которому должны следовать классы, без какой-либо реализации:
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");
}
}Ключевые различия:
- Класс может реализовывать несколько интерфейсов, но наследоваться только от одного абстрактного класса
- Используйте интерфейсы, когда несвязанным классам необходимо иметь общее поведение
- Используйте абстрактные классы, когда связанные классы имеют общий код и состояние
- Абстрактные классы могут иметь конструкторы, поля и модификаторы доступа
- Отдавайте предпочтение интерфейсам для обеспечения гибкости
Попробуйте сами
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'Необязательные параметрыИменованные аргументыПерегрузка методов