Расширение структуры и поведения родительского класса. Общедоступное наследование, добавление полей и методов
Ограничение структуры и поведения родительского класса. Защищенное и закрытое наследование
Переопределение поведения родительского класса. Переопределение и уточнение методов при наследовании
Отложенные методы и абстрактно-специфицированные классы
Множественное наследование и связанные с ним проблемы. Виртуальный базовый класс. Интерфейсы
Терминальные классы и методы
Фрагменты из презентации
Синтаксис наследования
class Base { // Описание базового класса }; class Derived: спецификатор_доступа Base [,спецификатор_доступа Base2, ...] { // Описание производного класса };
Поля класса при наследования
При создании экземпляра производного класса сначала создаются и инициализируются все поля базового класса, а затем поля, добавленные в производном классе
Логически можно считать, что производный класс агрегирует в себе базовый класс или в производном классе «упакован» базовый класс
Для инициализации полей базового класса используется конструктор базового класса
Конструкторы при наследовании
Как и другие методы конструкторы наследуются, но их использование имеет ряд особенностей
Если в конструкторе производного класса явно не вызывается конструктор базового класса, то компилятор сам вызывает конструктор по умолчанию базового класса
Если необходимо вызвать конструктор базового класса с параметрами, то конструктор указывается в строке инициализации
Тело конструктора базового класса всегда выполняется раньше тела конструктора производного класса
Деструкторы при наследовании
Как и другие методы деструкторы наследуются, но их использование имеет ряд особенностей
Деструктор производного класса не требует явно вызывать деструктор базового класса. В деструкторе производного класса компилятор автоматически генерирует вызовы базовых деструкторов
Тело деструктора производного класса всегда выполняется раньше тела деструктора базового класса
Пример вызова конструкторов и деструкторов при наследовании: ... (в полной версии, доступной для скачивания).
Расширение структуры и поведения родительского класса
Расширение структуры и поведения родительского класса осуществляется за счет общедоступного на- следования и добавления новых свойств и методов
При общедоступном наследовании в дочернем классе полностью сохраняется интерфейс родительского класса
Добавление методов при наследовании
Под добавлением методов понимается такая ситуация, когда в производном классе объявляются новые методы, которые отличаются от методов базового класса названием и/или набором параметров
При этом интерфейс родительского класса расширяется
Добавление методов используется при «расширении» базового класса, но может встречаться при его «обобщении» и «варьировании»
Ограничение структуры и поведения родительского класса
Под ограничением понимается частичное или полное сокрытие свойств и методов родительского класса в дочернем классе
Сокрытие свойств и методов класса выполняется для реализации таких форм наследования как «ограничение» и «конструирование»
Чаще всего для ограничения структуры и поведения родительского класса используют закрытое и защищенное виды наследования, которые полностью скрывают интерфейс родительского класса
Частичное сокрытие свойств и методов базового класса
Иногда требуется частичное сокрытие свойств и методов базового класса
В языке Си++ для отдельных свойств и методов можно задать свои спецификаторы доступа, от- личные от вида наследования
При этом уровень доступа не может быть повышен по сравнению с уровнем доступа, указанным в базовом классе. Самый привилегированный уровень доступа — это private
Переопределение поведения родительского класса
Переопределить поведение родительского класса можно за счет использования общедоступного на- следования и одновременного переопределения/замещения его методов
Переопределение/замещение методов используется при «специализации», «спецификации», «обобщении», «конструировании» и «варьировании» базового класса
Различие между переопределением и замещением будет рассмотрено при изучении полиморфизма
Переопределение/замещение методов при наследовании
Ситуация, когда в дочернем классе объявляется метод, совпадающий по названию и набору параметров с методом родительского класса, называется переопределением/замещением метода
Для пользователя дочернего класса переопределяемый/замещаемый метод базового класса уже не виден, но он существует в производном классе
Метод базового класса может быть вызван путем явного указания принадлежности к базовому классу
Уточнение методов при наследовании
Уточнение метода — это специализированная форма переопределения/замещения
В этом случае замещающий метод вызывает замещаемый метод родительского класса
Таким образом, родительское поведение сохраняется и присоединяется
Уточнение методов используется при «специализации», «обобщении», «конструировании» и «варьировании» базового класса
Отложенные методы
Отложенный метод (абстрактный метод) − это частный случай переопределения, когда метод базового класса не имеет реализации, а любая полезная деятельность задается в методе дочернего класса
Использование отложенных методов гарантирует единый интерфейс у всех потомков базового класса
Отложенные методы и абстрактные классы в языке Си++
В языке Си++ отложенный метод должен быть описан в явном виде с ключевым словом virtual
Тело отложенного метода не определяется, вместо этого функции «приписывается» значение 0: virtual <заголовок метода> = 0;
Отложенные методы в языке Си++ называются чи- сто виртуальными
В языке Си++ класс, который содержит чисто виртуальные методы, называется абстрактным
Свойства абстрактных классов
Компилятор не разрешает пользователю создавать экземпляры абстрактного класса
Только после того, как в производных классах будут переопределены отложенные методы, можно будет создавать экземпляры подклассов
Множественное наследование
Создание класса на основе двух и более базовых классов называется множественным наследованием
В этом случае производный класс обладает свойствами и методами сразу нескольких базовых классов
Через множественное наследование реализуется форма наследования «комбинирование»
Правильная форма «комбинирования» подразумевает, что производный класс может выступать в роли любого из своих родителей
Проблемы, связанные с множественным наследованием
Проблема двусмысленности имен возникает, когда в базовых классах имеются одноименные методы
Проблема наследования через общих предков возникает, когда диаграмма наследования имеет ромбовидную форму. В этом случае к проблеме двусмысленности имен добавляется проблема дублирования полей базового класса
Решение проблемы двусмысленности имен
Решение проблемы заключается в одновременном использовании замещения и переименования методов
Решение проблемы наследования через общих предков
Если мы хотим иметь только одну копию полей прародительского класса, то должны наследовать его как виртуальный базовый класс
Если базовый класс определен как виртуальный, то в производных классах создается единственная копия его полей. Кроме того, только один раз будет вызван конструктор по умолчанию прародительского класса
Уровни доступа при наследовании через общих предков
Спецификаторы доступа при наследовании могут отличаться в разных родительских классах
Следовательно, одни и те же элементы прародительского класса будут иметь различные спецификаторы доступа в производном классе
В таком случае наиболее жесткий уровень защиты игнорируется и используется менее ограничительная категория
Рекомендации по использованию множественного наследования
Из-за проблем, возникающих при множественном наследовании классов, многие языки программирования его не поддерживают
В современных ООП-языках вместо множественного наследования классу разрешено реализовывать несколько интерфейсов
Использование интерфейсов решает проблему наследования через общих предков
Интерфейсы
Интерфейс — это множество требований (requirements), предъявляемых к соответствующему классу
Требования выражаются в объявлении методов
Объявление интерфейса на языке Java: public interface <имя интерфейса> { // заголовки методов }
Свойства интерфейсов (применительно к языку Java)
В интерфейсе не может быть ни полей, ни статических методов, но могут объявляться статические константы
Нельзя создать экземпляр интерфейса, но можно объявить интерфейсную переменную
Интерфейсные переменные должны ссылаться на объект класса, реализующего данный интерфейс
Аналогично классам интерфейсы могут образовывать иерархию наследования
Реализация интерфейса выполняется каким-либо классом путем определения методов, указанных в интерфейсе
class <имя класса> implements <имя интерфейса> { // описание класса, в том числе, методов, // указанных в интерфейсе }
Интерфейсы и абстрактные классы
Интерфейсы и абстрактные классы — это схожие понятия, но имеются и различия между ними
Абстрактный класс может иметь состояние и поведение (хотя бы частично), а интерфейс только заявляет поведение
Интерфейс, чаще всего, определяет некоторое свойство, а абстрактный класс определяет «полноценную» сущность
Злоупотреблять интерфейсами не следует, т.к. они вводят много мелких понятий
Терминальные классы и методы
Классы, которые не могут иметь потомков, называются терминальными
Данный механизм отсутсвует в языке Си++, но имеется в языке Java: final class <имя класса> { ... }
Так же неизменными можно сделать отдельные методы класса: final <имя метода> { ... }
Использование терминальных классов и методов
Трудно предусмотреть все возможности повторного использования класса, особенно для классов общего назначения. Определяя класс или метод как final, вы блокируете возможность использования класса в проектах других программистов только потому, что сами не могли предвидеть такую возможность
Обычно терминальные классы и методы используются для обеспечения безопасности