printКлассы

printНаследование

Наследование – это такое отношение между классами, когда один класс повторяет структуру и поведение другого класса (одиночное наследование) или других (множественное наследование) классов. Обычно подклассы повторяют структуры их суперклассов. Поведение суперклассов также наследуется. В большинстве языков допускается не только наследование методов суперкласса, но также добавление новых и переопределение существующих методов. В Smalltalk любой метод суперкласса можно переопределить в подклассе. В C++ степень контроля за этим несколько выше. Функция, объявленная виртуальной, может быть в подклассе переопределена, а остальные  – нет.

14661.png

Самый общий класс в иерархии классов называется базовым. В многих языках языках программирования определен базовый класс самого верхнего уровня, который является единым суперклассом для всех остальных классов (в Smalltalk – object, в Delphi – TObject), в C++ хорошо сделанная структура классов – это скорее лес из деревьев наследования, чем одна многоэтажная структура наследования с одним корнем.
У класса обычно бывает два вида клиентов:
  • экземпляры;
  • подклассы.
Часто полезно иметь для них разные интерфейсы. В частности, мы хотим показать только внешне видимое поведение для клиентов-экземпляров, но нам нужно открыть служебные функции и представления клиентам-подклассам. Этим объясняется наличие открытой, защищенной и закрытой частей описания класса в языке C++: разработчик может четко разделить, какие элементы класса доступны для экземпляров, а какие для подклассов.
Есть серьезные противоречия между потребностями наследования и инкапсуляции. В значительной мере наследование открывает наследующему классу некоторые секреты. На практике, чтобы понять, как работает какой-то класс, часто надо изучить все его суперклассы в их внутренних деталях.
Наследование можно рассматривать, как способ управления повторным использованием программ, то есть, как простое решение разработчика о заимствовании полезного кода. В этом случае механика наследования должна быть гибкой и легко перестраиваемой. Другая точка зрения: наследование отражает принципиальную родственность абстракций, которую невозможно отменить. В Smalltalk эти два аспекта неразделимы. C++ более гибок. В частности, при определении класса его суперкласс можно объявить public. В этом случае подкласс считается также и подтипом, то есть обязуется выполнять все обязательства суперкласса, в частности обеспечивая совместимое с суперклассом подмножество интерфейса и обладая неразличимым с точки зрения клиентов суперкласса поведением. Но если при определении класса объявить его суперкласс как private, это будет означать, что, наследуя структуру и поведение суперкласса, подкласс уже не будет его подтипом. Это означает, что открытые и защищенные члены суперкласса станут закрытыми членами подкласса, и следовательно они будут недоступны подклассам более низкого уровня. Кроме того, тот факт, что подкласс не будет подтипом, означает, что класс и суперкласс обладают несовместимыми (вообще говоря) интерфейсами с точки зрения клиента.
Одиночное наследование при всей своей полезности часто заставляет программиста выбирать между двумя равно привлекательными классами. Это ограничивает возможность повторного использования предопределенных классов и заставляет дублировать уже имеющиеся коды.
Необходимость множественного наследования в OOP остается предметом горячих споров. Множественное наследование можно сравнить с парашютом: как правило, он не нужен, но, когда вдруг он понадобится, будет жаль, если его не окажется под рукой.
Проектирование структур классов со множественным наследованием – трудная задача, решаемая путем последовательных приближений. Есть две специфические для множественного наследования проблемы – как разрешить конфликты имен между суперклассами и что делать с повторным наследованием.
Конфликт имен происходит, когда в двух или более суперклассах случайно оказывается элемент (переменная или метод) с одинаковым именем. Борются с этим конфликтом тремя способами. Во-первых, можно считать конфликт имен ошибкой и отвергать его при компиляции (Smalltalk). Во-вторых, можно считать, что одинаковые имена означают одинаковый атрибут (так делает CLOS). В третьих, для устранения конфликта разрешается добавить к именам префиксы, указывающие имена классов, откуда они пришли (С++).
Проблема повторного наследования решается тремя способами. Во-первых, можно его запретить, отслеживая при компиляции (Smalltalk), во-вторых, можно явно развести две копии унаследованного элемента, добавляя к именам префиксы в виде имени класса-источника (C++), в-третьих, можно рассматривать множественные ссылки на один и тот же класс, как обозначающие один и тот же класс (виртуальные базовые классы C++)
При множественном наследовании часто используется прием создания примесей (mixin). Идея примесей происходит из языка Flavors: можно комбинировать (смешивать) небольшие классы, чтобы строить классы с более сложным поведением. Примесь синтаксически ничем не отличается от класса, но назначение их разное. Примесь не предназначена для порождения самостоятельно используемых экземпляров – она смешивается с другими классами, выражая какую-то одну какую-то особенность, которую можно привить другим классам через наследование. Эта особенность обычно ортогональна собственному поведению наследующего ее класса.
loading