В чем заключается различие между использованием абстрактного класса и интерфейса при определении контрактов для классов?
Ответ от нейросети
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.
Такой дизайн удобен, когда у всех реализаций общий алгоритм, но разные детали. Если бы нужен был только контракт без общей логики, достаточно было бы одного интерфейса.
Ключевые моменты:
- Интерфейс описывает контракт; абстрактный класс — контракт плюс общая реализация.
- Класс может реализовать несколько интерфейсов, но наследоваться только от одного класса.
- Интерфейсы удобны для слабой связанности и полиморфизма между разными иерархиями.
- Абстрактные классы полезны, когда есть общее состояние или общий алгоритм.
- Если нужно расширять контракт без жёсткой привязки к реализации, обычно выбирают интерфейс.
- Если нужно переиспользовать код в наследниках, чаще подходит абстрактный класс.