Реферат по предмету "Программирование"


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

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

Евгений Каратаев

В
этой работе разбирается проблема множественного наследования в языке
программирования С++ и возможное ее решение путем применения абстракций
интерфейсов.

Множественным
наследованием является образование класса путем наследования одновременно
нескольких базовых классов. Штука полезная и одновременно с этим проблемная.
Разберем пример, в котором появляется множественное наследование, приводящее к
проблеме.

Классическим
заданием для начинающего программиста является задача написать классы,
реализующие иерархию Человек - Студент - Сотрудник. Обычно первым же решением
есть образование трех классов в виде:

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:

      БытьСтудентом(){};

      virtual void Func( void);

      // пример функции, обращающейся к ядру
объекта

      {

         БытьЧеловеком* ptr = dynamic_cast( this);

         if( ptr)

         {

            // используем ядро

         }

      };

};

На
первый взгляд, приведение типа БытьСтудентом к типу БытьЧеловеком невозможно,
поскольку никто их этих классов ни от кого не наследован. Но дело в том, что
оператор dynamic_cast определен не для классов, а для объектов. И если при
исполнении кода Func реальный объект, для которого эта функция выполняется,
имееет класс, унаследованый от БытьЧеловеком, то оператор вернет правильное
значение. Согласно стандарту, оператор приведения типа dynamic_cast имеет два
вида поведения если приведение невозможно - вернуть нулевое значение либо
возбудить исключительную ситуацию. Оба варианта нас полностью устраивают.

Я
считаю, что в модели применения интерфейсных классов для решения проблем
множественного наследования будет также красиво построить интерфейсные классы с
конструкторами, не требующими обращения к ядру объекта. Впрочем, это уже из
области философии помехоустойчивого программирования.
Список литературы

Для
подготовки данной работы были использованы материалы с сайта http://karataev.nm.ru/


Не сдавайте скачаную работу преподавателю!
Данный реферат Вы можете использовать для подготовки курсовых проектов.

Поделись с друзьями, за репост + 100 мильонов к студенческой карме :

Пишем реферат самостоятельно:
! Как писать рефераты
Практические рекомендации по написанию студенческих рефератов.
! План реферата Краткий список разделов, отражающий структура и порядок работы над будующим рефератом.
! Введение реферата Вводная часть работы, в которой отражается цель и обозначается список задач.
! Заключение реферата В заключении подводятся итоги, описывается была ли достигнута поставленная цель, каковы результаты.
! Оформление рефератов Методические рекомендации по грамотному оформлению работы по ГОСТ.

Читайте также:
Виды рефератов Какими бывают рефераты по своему назначению и структуре.

Сейчас смотрят :

Реферат Философия Китая
Реферат Интерьер как средство характеристики героя По одному из произведений русской литературы XIX века
Реферат Этика средневековья и нового времени
Реферат Алгоритм анализа учителем проведенного урока
Реферат Формулы соуправления классом
Реферат Строительство мостов
Реферат «Актуальные вопросы информационной деятельности в системе торгово-промышленных палат России», прошедшем 22 июня в рамках работы совета руководителей торгово-промышленных палат. В дискуссии приняли участие руководители региональных палат, представител
Реферат Отчетный и плановый баланс рабочего времени
Реферат Ань Синь, Анна
Реферат Оценка актива, приносящего стабильную прибыль
Реферат Нові дослідження Волчанського городища
Реферат История Ясенево
Реферат Оформление документов
Реферат Если бы я стала президентом...
Реферат Экономическая природа кредита и его роль на современном этапе