Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Роджерсон Д. - Основы COM - 2000.pdf
Скачиваний:
412
Добавлен:
13.08.2013
Размер:
2.4 Mб
Скачать

109

интерфейса и передавать вызовы внутреннему компоненту (рис. 8-3). Однако внешний компонент не может специализировать какие-либо функции интерфейса. После того, как внешний компонент передаст интерфейс клиенту, тот обращается к внутреннему компоненту самостоятельно. Клиент не должен знать, что он работает с двумя разными компонентами, так как это нарушит инкапсуляцию. Задача агрегирования — заставить внешний и внутренний компоненты вести себя как один компонент. Как Вы увидите далее, эта возможность достигается при помощи QueryInterface.

Внешний компонент

IX

Внутренний компонент

IY

Рис. 8-3 Когда внешний компонент агрегирует интерфейс, он передает укаатель на него непосредственно клиенту. Он не реализуется интерфейс заново для передачи вызовов внутреннему компоненту.

Сравнение включения и агрегирования

Небольшой пример пояснит различия между включением и агрегированием. Предположим, что Вы хозяин небольшой металлоремонтной мастерской. У Вас есть две работницы — Памела и Анжела. Памела работает уже давно и знает свое дело досконально. Если заказ будет выполнять Памела, достаточно просто направить клиента к ней. Анжела, напротив, новичок и у нее нет опыта работы с металлом. Когда работу поручают ей, обычно нужно дать начальные указания, а иногда и сделать вместо нее наиболее сложную часть. Вы же должны и договариваться с клиентом. После того, как Анжела закончит работу, Вы вместе с ней должны проверить результаты и, может быть, что-то подправить. Случай Памелы сходен с подходом агрегирования. Дав работу, Вы уходите со сцены. Однако в случае Анжелы Вы по-прежнему ведете все переговоры с клиентом, даете начальные указания и проверяете работу в конце. Этот вариант аналогичен включению.

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

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

Поскольку агрегирование — это особый случай включения, мы рассмотрим включение первым.

Реализация включения

Включение компонента выполняется столь же просто, как и использование. Каталог \CHAP08\CONTAIN на прилагающемся к книге диске содержит пример кода включения. В этом примере Компонент 1 — внешний; он реализует два интерфейса: IX и IY. При этом он использует реализацию IY Компонентом 2 — внутренним, включаемым компонентом. Это в точности соответствует схеме рис. 8-2.

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

Нам остается рассмотреть только внешний компонент — Компонент 1, который включает Компонент 2. В приведенном ниже листинге 8-1 показано объявление и большая часть реализации Компонента 1. Я выделил полужирным некоторые участки кода, относящиеся к включению. Новая переменная-член m_pIY содержит указатель на интерфейс IY включаемого Компонента 2.

Код включения из CONTAIN\CMPNT1

///////////////////////////////////////////////////////////

//

// Компонент 1

//

class CA : public IX, public IY

{

110

public:

 

 

// IUnknown

 

 

virtual HRESULT

__stdcall QueryInterface(const IID& iid, void** ppv);

virtual ULONG

__stdcall

AddRef();

virtual ULONG

__stdcall

Release();

// Интерфейс IX

virtual void __stdcall Fx() { cout << "Fx" << endl; }

// Интерфейс IY

virtual void __stdcall Fy() { m_pIY->Fy(); }

//Конструктор

CA();

//Деструктор

~CA();

//Функция инициализации, вызываемая фабрикой класса для

//создания включаемого компонента

HRESULT __stdcall Init();

private:

//Счетчик ссылок long m_cRef;

//Указатель на интерфейс IY включаемого компонента

IY* m_pIY;

};

//

// Конструктор

//

CA::CA() : m_cRef(1), m_pIY(NULL)

{

::InterlockedIncrement(&g_cComponents);

}

//

// Деструктор

//

CA::~CA()

{

::InterlockedDecrement(&g_cComponents); trace("Самоликвидация");

// Освободить включаемый компонент if (m_pIY != NULL)

{

m_pIY->Release();

}

}

// Инициализация компонента путем создания включаемого компонента

HRESULT __stdcall CA::Init()

{

trace("Создать включаемый компонент");

HRESULT hr = ::CoCreateInstance(CLSID_Component2, NULL, CLSCTX_INPROC_SERVER, IID_IY, (void**)&m_pIY);

if (FAILED(hr))

{

trace("Не могу создать включаемый компонент"); return E_FAIL;

}

else

{

111

return S_OK;

}

}

Листинг 8-1 Компонент 1 создает экземпляр включаемого компонента и хранит указатель на интерфейс IY последнего

Давайте рассмотрим, как работает этот код внешнего Компонента 1. Новый метод под названием Init создает внутренний Компонент 2 тем же самым способом, которым создают компоненты все клиенты, — посредством вызова CoCreateInstance. При этом внешний компонент запрашивает указатель на IY у внутреннего и, в случае успеха, сохраняет его в m_pIY.

В приведенном листинге не показаны реализация QueryInterface и функций внешнего IUnknown. Она абсолютно та же, что и в случае, когда включение не используется. Когда клиент запрашивает у Компонента 1 интерфейс IY, тот возвращает указатель на свой интерфейс. Затем, когда клиент вызывает метод этого интерфейса, Компонент 1 передает вызов Компоненту 2. Это выполняет следующая строка:

virtual void Fy() { m_pIY->Fy(); }

Когда Компонент 1 самоликвидируется, его деструктор вызывает Release для указателя m_pIY, в результате чего Компонент 2 также удаляет себя.

Фабрика класса Компонента 1 мало изменилась по сравнению с фабрикой класса из предыдущей главы. Единственный новый момент — то, что функция CreateInstance вызывает после создания Компонента 1 его функцию Init. Код этой функции приведен в листинге 8-2.

Код функции CreateInstance из CONTAIN\CMPNT1

HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid,

void** ppv)

{

// Агрегирование не поддерживается if (pUnknownOuter != NULL)

{

return CLASS_E_NOAGGREGATION;

}

// Создать компонент

CA* pA = new CA; if (pA == NULL)

{

return E_OUTOFMEMORY;

}

// Инициализировать компонент

HRESULT hr = pA->Init(); if (FAILED(hr))

{

// Ошибка при инициализации. Удалить компонент pA->Release();

return hr;

}

// Получить запрошенный интерфейс hr = pA->QueryInterface(iid, ppv); pA->Release();

return hr;

}

Листинг 8-2 Фабрика класса внешнего компонента вызывает для вновь созданного компонента функцию Init

Вот и все, что необходимо для реализации включения. Теперь рассмотрим, для чего включение может применяться.

Расширение интерфейсов

Одно из основных применений включения — расширение интерфейса посредством добавления кода к существующему интерфейсу.

Рассмотрим пример. Имеется класс IAirplane (Самолет), который Вы хотите превратить в IFloatPlane (Гидросамолет). Определения интерфейсов приводятся ниже:

interface IAirplane : IUnknown

Соседние файлы в предмете Программирование на C++