Часть 5. Агрегация
До сих пор для получения интерфейса объекта я использовал функцию GetInterface. Она прекрасно работает и удобна в использовании, но имеет существенные ограничения. Прежде всего, эта функция не виртуальная. То есть, вы не сможете переопределить ее поведение в классах-наследниках. А делает эта функция только одно - сканирует локальную VMT объекта на предмет получения требуемого куска VMT. Однако, начиная от TComponent, компоненты Delphi содержат функцию, делающую почти то же самое, но являющуюся виртуальной. Под "почти" я имею ввиду то, что эта функция вызывает GetInterface, но осуществляет еще дополнительные проверки и имеет немного другой формат вызова. Эта функция в последствии принимает участие в COM программировании и имеет наименование QueryInterface .
Функция определяется так: function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall; Функция возвращает HResult (целое число, содержащее код ошибки) для определения успешности или не успешности ее выполнения. Для преобразования этого значения в boolean (если нет необходимости анализировать непосредственно код ошибки и вас интересует лишь фактическое "да" или "нет") имеется дополнительная функция Succeeded. Любой вызов GetInterface из приведенных выше примеров можно заменить на примерно следующий: if Succeeded(QueryInterface(IMyHello, MyHello)) then … Однако есть одна маленькая неприятность - функция QueryInterface описана в секции protected класса TComponent. Это означает, что вы не можете ее вызвать нигде, кроме как внутри методов данного класса TComponent. То есть, строка Application.MainForm.QueryInterface(…) не будет компилироваться. Из этого есть два выхода. Первый заключается в получении ЛЮБОГО интерфейса объекта через вызов GetInterface и через него вызывать функцию QueryInterface. Для этих целей можно написать обобщенную процедуру, скажем так function QueryInterface(const AObject: TObject, const IID: TGUID; out Obj): HResult; begin if AObject.GetInterface(IID, Obj) then Result := S_OK else Result := E_NOINTERFACE; end; Второй заключается в написании наследников всех (или почти всех) используемых базовых компонент, в которых эта функция перемещается в секцию public. Примерно вот так: type TInterfacedForm = class(TForm, IUnknown) public function QueryInterface(const IID: TGUID; out Obj): HResult; override; end; … implementation function TInterfacedForm .QueryInterface(const IID: TGUID; out Obj): HResult; begin Result := inherited QueryInterface(IID, Obj); end; end. Если при этом все необходимые компоненты проекта (в частности, главную форму приложения) наследовать от них, тогда в любой точке проекта можно будет построить следующий вызов. var MainForm: TInterfacedForm; begin if Application.MainForm is TInterfacedForm then begin MainForm := Application.MainForm is TInterfacedForm; if Succeeded(MainForm.QueryInterface(IMyHello, MyHello)) then … … end; end; Остается заметить, что этот модуль желательно оформить в виде отдельного пакета, установить его в системе и компилировать с ним как основное приложение, так и пакеты-плугины.
Теперь, после всего выше сказанного, нетрудно осуществить непосредственную агрегацию. Первое место, где она с успехом может быть применена - это приложения с использованием БД. Обычно в этом случае основное приложение имеет (помимо главной формы) один или несколько модулей данных (наследников TDataModule), содержащих коннект к БД и бизнес логику приложения. Чтобы явно подчеркнуть непосредственно агрегацию, сделаем главную форму не наследующей никакого интерфейса. Между тем, оказывается возможным (с помощью простой, но довольно обобщенной махинации) запрашивать требуемые дочерней форме интерфейсы и выполнять над ними работу. Исходный текст проекта см. в архиве (каталог Step5). Код проекта мал, упрощен насколько это возможно и вряд ли нуждается в особых комментариях.