Чем отличается интерфейс от абстрактного класса? В каких случаях вы использовали бы и то, и другое?csharp-134

Основные различия

Характеристика Интерфейс Абстрактный класс
Реализация Только сигнатуры членов Может содержать реализацию
Множественное наследование Да (можно реализовать много интерфейсов) Нет (только один базовый класс)
Модификаторы доступа Все члены public по умолчанию Могут быть protected, private и т.д.
Поля Не поддерживаются Могут содержать поля
Конструкторы Не поддерживаются Могут иметь конструкторы
Версионность Ломает существующий код при изменении Более безопасен для изменений
По умолчанию (default) Поддерживается с C# 8.0 Всегда требует переопределения

Когда использовать интерфейс

  1. Определение контракта для разнородных классов:
public interface ILoggable
{
    string GetLogMessage();
}

public class User : ILoggable { ... }
public class Order : ILoggable { ... }
  1. Множественное наследование поведения:
public class SmartDevice : IConnectable, IUpdatable, IDiagnosable
  1. Тестируемость (легко мокировать):
public interface IRepository
{
    void Save(Order order);
}

// Легко подменить реализацию для тестов
  1. Совместимость между разными иерархиями классов

Когда использовать абстрактный класс

  1. Общая базовая функциональность:
public abstract class PaymentProcessor
{
    protected abstract bool ProcessPayment(decimal amount);

    public void ExecutePayment(decimal amount)
    {
        Log("Начало обработки платежа");
        if (ProcessPayment(amount))
            Log("Платеж успешен");
        else
            Log("Ошибка платежа");
    }
}
  1. Шаблонный метод (Template Method pattern):
public abstract class DataImporter
{
    public void Import(string filePath)
    {
        Validate(filePath);
        var data = LoadData(filePath);
        Transform(data);
        Save(data);
    }

    protected abstract void Transform(Data data);
}
  1. Контроль версий базовой логики:
public abstract class RepositoryBase
{
    public virtual void Save(object entity) { ... }
    // Можно добавить новые методы без ломания наследников
}

Комбинированный подход

Лучшие практики часто включают оба подхода:

public interface IRepository<T>
{
    void Add(T entity);
    T GetById(int id);
}

public abstract class RepositoryBase<T> : IRepository<T>
{
    protected readonly DbContext _context;

    public RepositoryBase(DbContext context)
    {
        _context = context;
    }

    public virtual void Add(T entity)
    {
        _context.Set<T>().Add(entity);
    }

    public abstract T GetById(int id);
}

public class UserRepository : RepositoryBase<User>
{
    public override User GetById(int id)
    {
        return _context.Users.Find(id);
    }
}

Критические различия в производительности

  1. Вызов виртуальных методов:
  • Интерфейсы: чуть медленнее (2-3 такта процессора)
  • Абстрактные классы: оптимизированы CLR
  1. Размер объекта:
  • Каждый интерфейс добавляет entry в таблицу методов
  • Абстрактные классы не увеличивают накладные расходы

Современные тенденции

С появлением дефолтных методов в интерфейсах границы размываются:

public interface ILogger
{
    void Log(string message);

    // Новая функциональность без ломания реализаций
    void LogError(Exception ex) => Log($"ERROR: {ex.Message}");
}

Резюмируем:

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