Menu
Coddy logo textTech

Интерфейс 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# допускает реализации по умолчанию.

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

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

Шпаргалка

Интерфейс определяет контракт, которому должны следовать классы, без какой-либо реализации:

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

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

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