Характеристика | Интерфейс | Абстрактный класс |
---|---|---|
Реализация | Только сигнатуры членов | Может содержать реализацию |
Множественное наследование | Да (можно реализовать много интерфейсов) | Нет (только один базовый класс) |
Модификаторы доступа | Все члены public по умолчанию | Могут быть protected, private и т.д. |
Поля | Не поддерживаются | Могут содержать поля |
Конструкторы | Не поддерживаются | Могут иметь конструкторы |
Версионность | Ломает существующий код при изменении | Более безопасен для изменений |
По умолчанию (default) | Поддерживается с C# 8.0 | Всегда требует переопределения |
public interface ILoggable
{
string GetLogMessage();
}
public class User : ILoggable { ... }
public class Order : ILoggable { ... }
public class SmartDevice : IConnectable, IUpdatable, IDiagnosable
public interface IRepository
{
void Save(Order order);
}
// Легко подменить реализацию для тестов
public abstract class PaymentProcessor
{
protected abstract bool ProcessPayment(decimal amount);
public void ExecutePayment(decimal amount)
{
Log("Начало обработки платежа");
if (ProcessPayment(amount))
Log("Платеж успешен");
else
Log("Ошибка платежа");
}
}
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);
}
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);
}
}
С появлением дефолтных методов в интерфейсах границы размываются:
public interface ILogger
{
void Log(string message);
// Новая функциональность без ломания реализаций
void LogError(Exception ex) => Log($"ERROR: {ex.Message}");
}
интерфейсы лучше для определения контрактов и полиморфизма, абстрактные классы — для совместной реализации и шаблонных методов. В современных приложениях часто используют комбинацию обоих подходов для достижения гибкости и повторного использования кода.