Студенческий сайт КФУ - ex ТНУ » Учебный раздел » Учебные файлы »Информатика

Интерфейсы как решение проблем множественного наследования

Тип: доклад
Категория: Информатика
Скачать
Купить
Интерфейсы как решение проблем множественного наследованияЕвгений Каратаев В этой работе разбирается проблема множественного наследования в языке программирования С++ и возможное ее решение путем применения абстракций интерфейсов. Множественным наследованием является образование класса путем наследования одновременно нескольких базовых классов. Штука полезная и одновременно с этим проблемная. Разберем пример, в котором появляется множественное наследование, приводящее к проблеме. Классическим заданием для начинающего программиста является задача написать классы, реализующие иерархию Человек - Студент - Сотрудник. Обычно первым же решением есть образование трех классов в виде: class Человек { ... };class Сотрудник : public Человек { ... };class Студент : public Человек { ... };В классе Человек декларируются несколько виртуальных и, возможно, абстрактных, функций, которые переопределяются / реализуются в классах-наследниках. Схема на первый взгляд совершенно очевидна и практически ни у кого не вызывает подозрений. Схема реализуется в программе и программа сдается в работу. Проблема возникает позже, когда оператор приходит и говорит: - У меня есть человек, который одновременно и сотрудник и студент. Что мне делать? Реализованная схема, вообще говоря, не предполагает такого варианта - могут быть либо сотрудник, либо студент. Но что-то делать надо. В этот момент приходит на помощь множественное наследование. Программист, не долго думая, создает еще один класс, образованный наследованием и от Сотрудник и от Студент: class СтудентСотрудник : public Студент, public Сотрудник { ...};На первый взгляд все в порядке, на второй - полный бардак.  Дело в том, что класс Сотрудник, как он был декларирован, содержит в себе полную копию класса Человек. То же самое относится и к классу Студент. Таким образом, класс СтудентСотрудник будет содержать в себе уже 2 копии класса Человек. При этом функции класса Сотрудник будут работать со своим экземпляром класса Человек, а функции класса Студент - со своим. В результате корректного поведения добиться практически очень трудно. В классе СтудентСотрудник придется переопределять все функции базовых классов и вызывать соответствующие функции базовых классов, чтобы модификации обеих копий класса Человек прошли когерентно. Обнаружив такую ситуацию путем тяжелой отладки, программист приходит к необходимости применения виртуального наследования для исключения дублирования класса Человек. Проблема состоит в том, что виртуальное наследование требует модификации графа наследования базовых классов. Требуемая схема имеет вид: class Человек { ... };class Студент : virtual public Человек { ... };class Сотрудник : virtual public Человек { ... };class СтудентСотрудник : public Студент, public Сотрудник { ...};В этом варианте решена проблема однозначной входимости класса Человек во все классы. Но остается вопрос - не возникнет ли такой же проблемы и дальше с полученным классом СтудентСотрудник? И будет ли возможность произвести модификацию уже работающего кода? В такой ситуации руки могут опуститься - следует либо согласиться с существованием проблемного кода либо действительно идти на полную переработку программы. Тем не менее элегантное решение существует. Это реализация базовых классов по принципу интерфейсов. Язык С++ не содержит языковой поддержки интерфейсов в явном виде, поэтому будем их эмулировать. Принцип интерфейса состоит в том, что его задачей является не столько реализация класса, сколько его декларация. Нормализуем исходную задачу: class БытьЧеловеком { ... };class БытьСтудентом { ... };class БытьСотрудником { ... };Исходя из нормализованного множества классов, получим дополнение: class Человек : public БытьЧеловеком { ... };class Сотрудник : public БытьЧеловеком, public БытьСотрудником { ... };class Студент : public БытьЧеловеком, public БытьСтудентом { ...};class СтудентСотрудник : public БытьЧеловеком, public БытьСтудентом, public БытьСотрудником { ... };Формально говоря, такая схема построения классов вполне работоспособна за исключением того, что во многих случаях программисты относятся к интерфейсам слишком уж буквально - оставляют в них только абстрактные функции и реализуют эти функции только в классах-наследниках. В результате полностью выхолащивается идея повторного использования кода. Основанием для нереализации функций в интерфейсных классах обычно служит то, что в классе - интерфейсе нет "ядра" объекта. В нашем случае ядром объекта или классом, реализующим возможность существования объекта, может выступать класс БытьЧеловеком. Возможным решением проблемы является передача конструктору интерфейсного класса указателя на конструируемый объект с тем, чтобы его запомнить в своем частном поле данных и использовать при реализации функций интерфейса. Примерно по схеме: class БытьСтудентом{ БытьЧеловеком& m_БытьЧеловеком; public: БытьСтудентом( БытьЧеловеком& init) : m_БытьЧеловеком( init) { ... };};class Студент : public БытьЧеловеком, public БытьСтудентом{ public: Студент() : БытьЧеловеком(), БытьСтудентом( *this) { ...};};В этой схеме, согласно стандарту, также есть проблема - стандарт не гарантирует инициализации конструкторов, указанных в списке инициализации, в том порядке, в котором они перечислены в этом списке. Поэтому мы, передавая *this как аргумент конструктора базового класса, получаем ссылку на негарантированно определенный объект. Выйти из этой ситуации можно, если декларировать конструктор без аргументов и создать дополнительную функцию инициализации, зависящую от *this. Но дублирование ссылок, хранимых в интерфейсных классах, тем не менее, сохраняется и это есть некрасиво. Для решения этой задачи есть чрезвычайно красивое, на мой взгляд, решение. Решение заключается в том, чтобы не хранить ссылку на ядро объекта, а получать ее динамически. Для этого применяется оператор приведения типа dynamic_cast, применяемый не к классу, а к объекту в процессе работы программы. Пример: class БытьСтудентом{ public:
Другие файлы:

Понятие наследования по действующему законодательству Российской Федерации
Характеристика оснований наследования, порядок правопреемства. Сущность наследственной трансмиссии, анализ субъектов наследственных правоотношений. Ос...

М.Гук. "Аппаратные интерфейсы ПК"
Книга посвящена аппаратным интерфейсам, использующимся в современных персональных компьютерах и окружающих их устройствах. В ней подробно рассмотрены...

Правовое регулирование наследования в Российской Федерации
Понятие и виды наследования по Гражданскому законодательству Российской Федерации. Общественные отношения, складывающиеся в сфере наследования. Исслед...

Наследственные отношения в Российской Федерации
Понятие и виды наследования. Правовое регулирование института наследования. Виды наследования в Российской Федерации. Понятие обязательной доли в насл...

Виды наследования. Органы управления в Беларуси
Понятие и общие положения наследования, особенности его отражения в современном законодательстве. Основания и разновидности наследования. Классификаци...