Композиция или наследование
Вопрос
Когда вы выбираете наследование, а когда композицию? Почему говорят «предпочитайте композицию»?
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Наследование создаёт жёсткую связь с базовым классом и хрупкость при его изменении
Наследование оправдано для настоящего отношения «является» и общих шаблонных методов
Композиция: объект содержит зависимости и делегирует им, поведение меняется подстановкой
Композиция хорошо дружит с DI и тестами (моки зависимостей)
В C# один базовый класс, поэтому иерархии быстро упираются в ограничения
Пример: декоратор над интерфейсом вместо наследника с переопределением
Что могут спросить следом
- Что такое проблема хрупкого базового класса?
- Как сделать кеширующий репозиторий без наследования?
Разбор
Наследование сильнее всего связывает классы: наследник зависит от деталей реализации базы, и изменение базового класса может сломать всех потомков. Это называют проблемой хрупкого базового класса. К тому же в C# базовый класс один, и глубокие иерархии быстро упираются в ограничения.
Поэтому по умолчанию я выбираю композицию: класс получает зависимости через конструктор и делегирует им работу. Поведение меняется подстановкой другой реализации, а в тестах можно подставить мок.
Хороший пример: нужно добавить кеширование к репозиторию. Вместо наследника с override пишу декоратор CachedUserRepository : IUserRepository, который принимает внутренний репозиторий и IMemoryCache. Наследование оставляю для настоящего отношения «является» и для шаблонного метода, когда базовый класс задаёт алгоритм, а наследники заполняют шаги, как BackgroundService.ExecuteAsync.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Термины по теме
Ещё по теме «ООП в C#»
- Абстрактный класс или интерфейс
- protected internal и private protected
- Методы интерфейса по умолчанию
- Явная реализация интерфейса
- Банковские счета: инкапсуляция и инварианты
- Фигуры: полиморфная площадь
- Чем new отличается от override
- new и override при вызове через базовый тип