Наследование реализаций: закопайте стюардессу
Ключевое противоречие ООП
Как известно, классическое ООП покоится на трех китах
- Инкапсуляция [1]
- Наследование [2]
- Полиморфизм [3]
Классическая же реализация по умолчанию:
- Инкапсуляция — публичные и приватные члены класса
- Наследование — реализация функционала за счет расширения одного класса-предка, защищенные члены класса.
- Полиморфизм — виртуальные методы класса-предка.
Но еще в 1986 году была обозначена серьезнейшая проблема [4], кратко формулируемая так:
Наследование ломает инкапсуляцию
- Классу-потомку доступны защищенные члены класса-предка. Всем остальным доступен только публичный интерфейс класса. Предельный случай взлома —
антипаттерн Паблик Морозов [5]
- Реально изменить поведение предка можно только с помощью перекрытия виртуальных методов.
- Принцип подстановки Лисков [6] обязывает класс-потомок удовлетворять всем требованиям к классу-предку.
- Для выполнения пункта 2 в точном соответствии с пунктом 3 классу-потомку необходима полная информация о времени вызова и реализации перекрытого виртуального метода
- Информация из пункта 4 зависит от реализации класса-предка, включая приватные члены и их код.
В теории мы уже имеем былинный отказ, но как насчет практики?
- Зависимость, создаваемая наследованием, чрезвычайно сильна.
- Наследники гиперчувствительны к любым изменениям предка.
- Наследование от чужого кода добавляет адскую боль при сопровождении:
разработчики библиотеки рискуют получить обструкцию из-за поломанной обратной совместимости при малейшем изменении базового класса, а прикладники — регрессию при любом обновлении используемых библиотек.
Все, кто используют фреймворки, требующие наследования от своих классов (WinForms, WPF, WebForms, ASP.NET), легко найдут подтверждения всем трем пунктам в своем опыте.
Неужели все так плохо?
Теоретическое решение
Влияние проблемы можно ослабить принятием некоторых конвенций
- Защищенные члены не нужны.
Это соглашение ликвидирует пабликов морозовых как класс.
- Виртуальные методы предка ничего не делают.
Это соглашение позволяет сочетать знание о реализации предка с независимостью от нее реализации уже в потомке.
- Виртуальные методы предка никогда не вызываются в его коде.
Это соглашение позволяет потомкам не зависеть от внутренней реализации предка, а также требует публичности всех виртуальных методов.
- Экземпляры предка никогда не создаются
Это соглашение позволяет избавиться от несоответствия требований к виртуальными методам (публичный контракт класса) с одной стороны и обязанностью ничего не делать (защищенный контракт класса) с другой. Теперь принцип подстановки Лисков можно соблюсти, не вступая в порочную связь с закрытым содержимым предка.
- Невиртуальных членов у предка нет
С учетом предыдущих соглашений невиртуальные члены предка становятся бесполезными и подлежат ликвидации.
Результат: если класс-предок состоит из публичных виртуальных пустых методов и требований к ним для потомков, то наследование уже не ломает инкапсуляцию. Что и требовалось доказать.
Попутно получаем возможность решение проблемы ромба для случая множественного наследования от конвенционных предков.
Но это все теория, а нам нужны...
Практические решения
- Виртуальные методы-пустышки уже есть во многих языках и носят гордое звание абстрактных [7].
- Классы, экземпляры которых создавать нельзя, тоже есть во многих языках и даже имеют то же звание [8].
- Полное соблюдение указанных соглашений в языке C++ использовалось как паттерн для проектирования и реализации Component Object Model [9].
- Ну и самое приятное: в C# и многих других языках соглашения реализованы как первоклассный элемент "интерфейс" [10].
Происхождение названия очевидно — в результате соблюдения соглашений от класса остается только его публичный интерфейс. И если множественное наследование от обычных классов — редкость, то от интерфейсов оно доступно без всяких ограничений.
Итоги
- Языки, где нет наследования от классов, но есть — от интерфейсов (например, Go), нельзя лишать звания объектно-ориентированных. Более того, такая реализация ООП правильнее теоретически и безопаснее практически.
- Наследование от обычных классов (имеющих реализацию) — чрезвычайно специфический и крайне опасный архаизм.
- Избегайте наследования реализаций без крайней необходимости.
- Используйте модификатор sealed (для .NET) или его аналог для всех классов, кроме специально спроектированных для наследования реализации.
- Избегайте публичных незапечатанных классов: пока наследование не выходит за рамки своих сборок, из него еще можно извлечь пользу и ограничить вред.
PS: Дополнения и критика традиционно приветствуются.
Автор: Bonart
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/189497
Ссылки в тексте:
[1] Инкапсуляция: https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%BA%D0%B0%D0%BF%D1%81%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
[2] Наследование: https://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
[3] Полиморфизм: https://habrahabr.ru/post/37576/
[4] в 1986 году была обозначена серьезнейшая проблема: http://dl.acm.org/citation.cfm?id=28702
[5] Паблик Морозов: http://dic.academic.ru/dic.nsf/ruwiki/377452
[6] Принцип подстановки Лисков: https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF_%D0%BF%D0%BE%D0%B4%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8_%D0%91%D0%B0%D1%80%D0%B1%D0%B0%D1%80%D1%8B_%D0%9B%D0%B8%D1%81%D0%BA%D0%BE%D0%B2
[7] абстрактных: https://ru.wikipedia.org/wiki/%D0%90%D0%B1%D1%81%D1%82%D1%80%D0%B0%D0%BA%D1%82%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4
[8] звание: https://ru.wikipedia.org/wiki/%D0%90%D0%B1%D1%81%D1%82%D1%80%D0%B0%D0%BA%D1%82%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BB%D0%B0%D1%81%D1%81
[9] Component Object Model: http://igrocoder.ru/tiki-index.php?page=%D0%A2%D0%B5%D1%85%D0%BD%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D1%8F+COM+(Component+Object+Model)
[10] "интерфейс": https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_(%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
[11] Источник: https://habrahabr.ru/post/310314/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.