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


Полнотекстовый поиск по базе:

Главная >> Доклад >> Информатика, программирование


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




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

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

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

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

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

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/

Похожие работы:

  • Объектно-ориентированная СУБД (прототип)

    Реферат >> Информатика, программирование
    ... Это позволяет решить проблему множественного насле­дова­ния. Вводит­ся поня­тие ... Уточнение методов решения задачи 2.1 Наследование Наследование является мощным ... интерфейсов в интерфейс порож­даемого класса или объекта, – как результат, полученный интерфейс ...
  • Объектно-Ориентированное программирование

    Реферат >> Информатика, программирование
    ... – Реализует полный алгоритм решения задачи. PASCAL жестко ... есть множественное наследование . Множественное наследование – мощное средство языка С++, но порождает ряд проблем. Не ... Разделы класса. Интерфейс класса. Переход к ООП как этап развития технологии ...
  • Turbo C++ Programer`s guide

    Реферат >> Информатика, программирование
    ... подпрограмм интерфейса нижнего уровня, как например, ... новые возможности, связанные с использованием множественного наследования и прочими средствами, появившимися в ... С++ нацелена на решение нескольких проблем, решаемых стандартными библиотечнымифункциями ...
  • Базы данных и информационные технологии

    Учебное пособие >> Информатика, программирование
    ... . Очевидное преимущество - стандартность интерфейса. Клиентские части любой SQL- ... ). Каким же образом можно решать эту проблему? Имеются компромиссные решения, ... и методов. Возможные при множественном наследовании двусмысленности (по именованию атрибутов ...
  • VB, MS Access, VC++, Delphi, Builder C++ принципы(технология), алгоритмы программирования

    Реферат >> Информатика, программирование
    ... четыре процедуры, которые используют как множественную, так и непрямую рекурсию. ... , может вызвать эту проблему. Решение ее состоит в том ... ответ на вопрос: «Какое решение задачи будет наилучшим?» ... Наследование, 378 О Объект вид, 391 единственный, 387 интерфейс, ...
  • Корпоративные сети

    Реферат >> Информатика, программирование
    ... как средство защиты людей от вредного излучения, а экранирование связано с решением проблем ... типов коллекций, ссылок, множественного наследования и репликации данных. Определяемые ... : URL-интерфейс (UniversalResourceLocator). Этот интерфейс дает разработчикам ...
  • Обработка ошибок в коде программ РНР

    Учебное пособие >> Информатика, программирование
    ... чтобы решить проблему, воспользуемся полезным ... Множественная классификация оказывается как нельзя кстати при работе с исключениями. С использованием интерфейсов ... исключения. Пример. Решение, которое мы ... объединены в иерархию наследования, что позволяет при ...
  • С/C++

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

    Дипломная работа >> Языкознание, филология
    ... естественно-языковым интерфейсом стараются выражаться как можно короче, ... системы, направленные на решение проблем и т.д.) Суть этих ... В основе теории наследования лежит теория силлогизмов ... касающегося реализации морфемы множественного числа в различных ...
  • AGraph: библиотека классов для работы с помеченными графами

    Курсовая работа >> Информатика, программирование
    ... решения теоретико-графовых задач использовались и непроцедурные языки, такие, как ... Существует GTL-Java интерфейс, позволяющий Java- ... Object Pascal - шаблоны и множественное наследование. Данное обстоятельство привело к реализации ... Одна из проблем, которые ...