Menu
Coddy logo textTech

Интерфейс vs абстрактный класс

Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy. Урок 26 из 70.

И интерфейсы, и абстрактные классы позволяют Define контракты, которым должны следовать другие классы, но они служат разным целям и обладают различными возможностями.

interface определяет, что должен делать class, не предоставляя никакой реализации. Это чистый контракт:

public interface IFlyable
{
    void Fly();
}

public class Bird : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("Flapping wings");
    }
}

Класс abstract class может предоставлять как контракт, так и общую реализацию. Это частичный шаблон:

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 для птиц и самолётов). Используй абстрактный класс, когда связанные классы имеют общий код и состояние (например, транспортные средства используют общий механизм запуска двигателя).

abstract class могут иметь constructor, поля и модификаторы доступа у членов. Традиционно интерфейсы этого не могли, хотя современный C# допускает реализации по умолчанию.

Если сомневаешься, предпочитай интерфейсы ради гибкости — ты всегда сможешь добавить abstract class позже, если станет необходима общая реализация.

challenge icon

Задание

Легко

Давайте создадим систему медиаплеера, которая демонстрирует, когда следует использовать интерфейсы, а когда — абстрактные классы. Вы разработаете структуру, в которой несвязанные устройства используют общее поведение воспроизведения через интерфейс, а связанные типы мультимедиа используют общую реализацию через абстрактный класс.

Вы распределите код по четырём файлам:

  • 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. Абстрактный класс обеспечивает повторное использование кода для связанных типов мультимедиа, а интерфейс позволяет любому несвязанному классу быть "playable" без общей иерархии наследования.

Попробуйте сами

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
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C#