Шрифт:
— Многие программисты предпочитают писать свой код, а не изучать чужой.
— После изучения кода библиотеки часто возникает соблазн его модификации, адаптации под конкретное приложение. В результате принцип повторного использования кода нарушается, т. к. приложение не сможет использовать новые версии этой библиотеки.
— Код библиотеки включается в код всех использующих ее приложений,
? Упаковка класса в динамически компонуемую библиотеку (DLL)
При этом мы избавляемся от проблем, возникающих при распространении классов в виде исходного кода. Но, естественно, возникают новые проблемы:
— Отсутствие двоичного стандарта для C++
Различные компиляторы с C++ по разному решают вопросы реализации отдельных языковых особенностей языка C++ и некоторые вопросы компоновки. В связи с этим, в общем случае нельзя гарантировать, что DLL, подготовленная на одном компиляторе, будет работать с клиентом, подготовленным с помощью другого компилятора.
— Проблема версий DLL
C++ поддерживает синтаксическую инкапсуляцию, но не двоичную. Иными словами, от клиента скрыта реализация класса на уровне языка, что позволяет менять реализацию класса не меняя кода клиента. Но при этом перекомпиляция клиента в общем случае необходима. Это связано с тем, что именно клиент отводит память под все данные экземпляра класса. Это делается ради повышения эффективности. В результате, если при изменении реализации класса был изменен состав (иили порядок) данных класса, то клиент не будет работать с новей версией DLL без перекомпиляции. Установив новую версию DLL, мы погубим все приложения, которые были скомпилированы для работы со старой версией.
• Не решены вопросы разработки распределенных приложений
Собственно, такая задача и не ставилась при разработке ООП. Однако в современных условиях распределенность приложений становится необходимым требованием.
Компонентное программирование
В данном разделе будет дано только очень короткое введение в проблематику компонентного программирования. Подробнее — в последующих главах.
Компонентное программирование — попытка решить те проблемы, которые возникают при использовании ООП. Основная идея — распространение классов в бинарном виде (т. е. не в виде исходного кода) и предоставление доступа к методам класса через строго определенные интерфейсы, что позволяет снять проблему несовместимости компиляторов и обеспечивает смену версий классов без перекомпиляции использующих их приложений.
Здесь стоит отметить, что роль интерфейсов в СОМ значительно более важная, чем роль посредника. Все в СОМ начинается с интерфейсов. Они определяются первыми и задают семантику некоторого сервиса. Различные классы могут реализовывать заданный интерфейс, обеспечивая тем самым полиморфизм на новом уровне.
Имеются различные технологии, реализующие парадигму компонентного программирования. Среди них COM (DCOM, СОМ+), С ORB A, Net.
Для определенности остановимся на подходе, используемом в СОМ.
Компонент — это хранилище (в виде DLL или EXE файла) для одного или нескольких классов. Все, что знает клиент о классе, это его уникальный идентификатор и один или несколько интерфейсов, обеспечивающих доступ к реализованным данным классом методам. Допускается реализация компонента и использующего его клиента на различных языках (Visual C++, Visual Basic). В реестре системы хранится информация о местоположении компонента, содержащего данный класс (на локальном или удаленном компьютере). Это позволяет системе прозрачно для клиента перенаправлять вызовы методов к нужному компоненту и возвращать результаты.
Таким образом, обеспечивается выполнение двух важных принципов компонентного программирования:
• независимость от языка программирования,
Основные особенности компонентного программирования можно коротко охарактеризовать следующим образом:
• Инкапсуляция
В СОМ инкапсуляция находится на более высоком уровне чем в ООП. Между клиентом и реализацией класса находятся интерфейсы. Интерфейс — абстрактный базовый класс, который не имеет элементов данных и который является прямым потомком не более чем одного другого интерфейса. Реализация методов данного интерфейса выполняется в классе, который является потомком данного и, возможно, еще других интерфейсов.
При соблюдении данных ограничений различные компиляторы генерируют эквивалентный код для вызовов методов интерфейса со стороны клиента. Клиент без перекомпиляции может вызывать методы новой версии класса (при сохранении интерфейса). Новая версия может иметь расширенную функциональность за счет добавления новых интерфейсов. Эти новые интерфейсы могут использоваться новыми клиентами, старые же клиенты продолжают работать со старыми интерфейсами, не зная о существовании новых.
• Наследование интерфейсов
В отличие от ООП, технология компонентного программирования не поддерживает наследование реализации класса. Наследуются только интерфейсы. Каждый интерфейс получает свой уникальный идентификатор и может реализовываться в различных классах, включаемых в различные компоненты. Новый интерфейс может наследовать ранее описанным интерфейсам. Например, в СОМ, любой интерфейс должен наследовать стандартному интерфейсу IUnknown. Наследование интерфейса в частности означает, что при реализации методов нового интерфейса будут реализованы и все методы, описанные в наследуемом интерфейсе.