На этом шаге мы рассмотрим особенности повторого использования объектов в C++ и COM.
Обычно при создании программ на C++ определение существующего класса можно применять повторно, прибегая к методам вложения или наследования. Вложение заключается в объявлении объекта в области видимости класса, как показано в следующем примере:
#include "Acme.h" // Содержит определение класса AcmeViewer. // Определяет функции-члены SetFile() и Display(). class MyViewer { protected: AcmeViewer m_obj; public: MyViewer() {m_obj.SetFile("C:\images\scully.gif", AV_TYPE_GIF);} void DisplayGif() {m_obj.Display(); } };
Здесь определяется простой класс MyViewer, содержащий экземпляр определенного ранее класса AcmeViewer. MyViewer повторно использует код, содержащийся в AcmeViewer. Конструктор MyViewer инициализирует вложенный объект AcmeViewer, и функция MyViewer::DisplayGif() пользуется услугами последнего. Класс MyViewer способен управлять доступом к объекту AcmeViewer и его функциям.
Наследование - мощная методика повторного использования кода, представляющего собой краеугольный камень C++. С учетом сказанного ранее о классе AcmeViewer становится очевидным, что следующее объявление:
#include "Acme.h" class MyViewer : public AcmeViewer { public: MyViewer() {SetFile("C:\images\scully.gif", AV_TYPE_GIF);} }
позволит Вам вызывать открытые или защищенные функции-члены AcmeViewer как функции-члены, принадлежащие объекту MyViewer:
MyViewer aViewer; aViewer.Display();
COM поддерживает вложение, но реализация наследования в этой технологии отличается от принятой в C++. В данном примере класс MyViewer наследует функциональность класса AcmeViewer. MyViewer доступны как открытые, так и защищенные функции-члены AcmeViewer. Этот тип наследования известен под названием наследования реализации (implementation inheritance).
СОМ наследование реализации не поддерживает. В этой технологии существует строгое логическое разделение между интерфейсом предоставляемым СОМ-объектом, и реализацией этого интерфейса. Опубликованный СОМ-интерфейс, идентифицируемый в глобальном масштабе по его GUID, является неизменным (immutable) - он никогда не будет модифицирован. Хотя число СОМ-объектов, реализующих тем или иным способом его функциональность, не ограничено, клиенты всегда будут знать, как обращаться к любому из этих объектов. Интерфейс является соглашением между СОМ-сервером и клиентом, в котором четко определено, какие данные объект получает а какие - возвращает.
Наследование реализации приводит к зависимости производного класса от базового. При изменении реализации базового класса производные в некоторых случаях перестают работать корректно, и поэтому иногда приходится переписывать их заново. Это весьма сложно в крупномасштабных разработках, особенно если у Вас нет доступа к исходному тексту базовых классов. Решение таких проблем несколько упрощается при отделении интерфейса от его реализации в СОМ; но это также означает, что Вы не можете повторно использовать СОМ-компоненты, создавая их производные, как в случае с классами C++.
В СОМ поддерживается форма наследования, известная как наследование интерфейса (interface inheritance). В C++ интерфейсы реализованы в виде абстрактных классов, содержащих только виртуальные функции, определяющие, но не реализующие методы интерфейса. Наследуя интерфейс, Вы определяете структуру таблицы vtable, в которой будут находиться указатели на экземпляры методов. Например, следующее определение создаст должным образом структурированную vtable с указателями на три метода IUnknown и на методы, определенные в IEncoder:
IEncoder : public IUnknown { // Здесь объявляются методы IEncoder. }
Именно благодаря неизменности СОМ-интерфейсов становится возможным их наследование. Вы, создавая производные СОМ-интерфейсы из любого другого СОМ-интерфейса, полностью уверены, что никто не изменит определение интерфейса родителя и не нарушит структуру Вашей vtable.
На следующем шаге мы рассмотрим один из способов повторного использования COM-объектов - вложение.