Sobes.tech
Назад к вопросам
Junior — Middle
55

В чем заключается различие между использованием абстрактного класса и интерфейса при определении контрактов для классов?

Ответ от нейросети

sobes.tech AI

Что хотят услышать интервьюеры:

Абстрактный класс выбирают, когда нужно дать общую базовую реализацию и разделяемое состояние для группы классов. Интерфейс используют, когда важно описать контракт без привязки к конкретной реализации и поддержать множественное поведение. Обычно интерфейс отвечает на вопрос “что умеет объект”, а абстрактный класс — “что у него уже есть общего”.

Определение:

Абстрактный класс — это базовый класс, который нельзя создать напрямую. Он может содержать как абстрактные члены, так и реализованные методы, поля, свойства и конструкторы, то есть задаёт и контракт, и частичную реализацию.

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

Пример использования:

Если есть система уведомлений, то можно сделать интерфейс INotifier для любого способа отправки сообщения, а абстрактный класс NotifierBase — для общей логики, например проверки текста и логирования.

public interface INotifier
{
    void Send(string message);
}

public abstract class NotifierBase : INotifier
{
    public void Send(string message)
    {
        if (string.IsNullOrWhiteSpace(message))
            throw new ArgumentException("Message is empty");

        Log(message);
        SendCore(message);
    }

    protected abstract void SendCore(string message);

    protected virtual void Log(string message)
    {
        Console.WriteLine($"Sending: {message}");
    }
}

public class EmailNotifier : NotifierBase
{
    protected override void SendCore(string message)
    {
        Console.WriteLine($"Email: {message}");
    }
}

Пояснение кода:

Код показывает оба подхода на одном примере.
INotifier задаёт контракт: любой уведомитель должен иметь метод Send.
NotifierBase уже содержит общую реализацию Send: проверяет сообщение, выполняет логирование и вызывает абстрактный SendCore, который обязаны реализовать наследники.
EmailNotifier использует готовую логику базового класса и реализует только конкретную отправку через email.

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

Ключевые моменты:

  • Интерфейс описывает контракт; абстрактный класс — контракт плюс общая реализация.
  • Класс может реализовать несколько интерфейсов, но наследоваться только от одного класса.
  • Интерфейсы удобны для слабой связанности и полиморфизма между разными иерархиями.
  • Абстрактные классы полезны, когда есть общее состояние или общий алгоритм.
  • Если нужно расширять контракт без жёсткой привязки к реализации, обычно выбирают интерфейс.
  • Если нужно переиспользовать код в наследниках, чаще подходит абстрактный класс.