Как стать автором
Обновить

Делегирование для ООП (Design Patterns) и самый эффективный способ взаимодействия объектов

Уровень сложностиСложный
Время на прочтение10 мин
Количество просмотров12K
Всего голосов 10: ↑4 и ↓60
Комментарии14

Комментарии 14

"класс А является частью объекта класса В" - мне почему-то кажется, что ситуация обратная.

Тоже заметил, видимо ошибка

надо же 15 раз перечитал и не заметил! исправил, спасибо.

Почему вы запрос интерфейса обозвали делегированием? Этак вы любой метод, который возвращает какой-угодно объект, должны называть делегатом.

А почему я не имею права назвать запрос интерфейса делегированием? В вашем единственном аргументе закралась ошибка (или вы хотите обсуждать что-то другое, не то что написано в статье), делегатом надо называть возвращаемый интерфейс, а не функцию, которая его возвращает, как я это понимаю.

В этом случае действительно любая функция, которая возвращает интерфейс (а не "какой-угодно объект"), она делегирует этот интерфейс наружу из класса. И делегат достается для работы с ним тому, кто его запросил.

интересно что нельзя создать COM-объект (его класс) без реализации такой функции, которая возвращает интерфейс (делегированный интерфейс). Оказывается очень полезно иметь такую функцию в корне иерархии.

Мне кажется, интерфейс это более сложное понятие чем делегат.

Делегат предполагает только одну функцию, а интерфейс несколько.

Поэтому нет смысла использовать термин делегат, когда речь идет об интерфейсе. То есть все это полезно только для понимания, это не значит что надо менять что то на практике, опять же это (возможно) только я так думаю.

А почему я не имею права назвать запрос интерфейса делегированием?

Да, не имеете права. Через QueryInterface вы можете возвращать только те интерфейсы, которые сами реализуете. Следовательно, делегация самих себя не может быть по определению.

Через QueryInterface вы можете возвращать только те интерфейсы, которые сами реализуете.

Я боюсь вы не правы:

Через QueryInterface класс возвращает (может возвращать, то есть это точно не запрещено!) интерфейсы которые у него есть в наличии (например в виде указателей-полей). Этот класс совершенно не обязан их реализовывать-имплементировать, он может, даже получить такой интерфейс из какой-то частной ДЛЛ-ки, но будет возвращать его как "часть себя" по сути! Как раз эта техника (что-ли) открывает просто поразительные возможности композиции объектов, я этим активно пользуюсь, более того DirectShow документация рекомендует, во многих случаях (если не в большинстве) такую технику! Я просто следую инструкциям из документации, в этом смысле.

Таким образом объекты (через свой класс) получают возможность повторно(!) использовать уже реализованную функциональность в других объектах, способом который снаружи неотличим от того, как если бы эти объекты имели собственную (копи-паст) реализацию такой функциональности. Как видите, это позволяет исключить копи-паст!

Идея настолько простая что до нее, действительно, трудно снизойти (что-ли).

Я сначала просто следовал инструкциям из документации, писал код, а когда я осознал как это великолепно работает, а главное на какой простой идее это базируется меня торкнуло :) ! Я лет 15 мечтал с кем то разделить эту "тайну", спасибо Хабру за эту возможность.

он может, даже получить такой интерфейс из какой-то частной ДЛЛ-ки, но будет возвращать его как "часть себя" по сути

Не может. Иначе у вас нарушаются правила QueryInterface:

  1. «Для любого экземпляра объекта вызов QueryInterface с IID_IUnknown всегда должен возвращать одно и то же значение физического указателя». То есть запросив IID_IUnknown из чужого интерфейса мы получим другой указатель, а надо — наш.

  2. «Должен быть возможен успешный запрос для любого интерфейса в объекте из любого другого интерфейса». Если мы из IFoo получили IBar, то должна быть верна и обратная операция: из IBar мы также должны получить IFoo. Из чужого объекта мы не можем получить себя, потому что чужой объект ничего про нас не знает.

Правила реализации QueryInterface

Именно поэтому вы можете возвращать только те интерфейсы, которые реализуете. А возвращать самого себя — это никакая не делегация.

Не может. Иначе у вас нарушаются правила QueryInterface:

Правила не нарушаются они переводятся в разряд «подраздела правил», если можно так выразиться (так же как теория относительности не отменяет классическую механику, а оставляет ее подразделом, так сказать). Вы наверно не обратили внимание, я упоминал DirectShow.

В документации DirectShow вы можете найти вот такой интересный интерфейс, INonDelegatingUnknown:

https://learn.microsoft.com/en-us/windows/win32/directshow/inondelegatingunknown

который разрешает поддержку делегируемого и non-делегируемого IUnknown интерфейсов (… to enable support for both nondelegating and delegating IUnknown interfaces in the same COM object) как это не странно звучит. (Мне кажется, такие обороты вполне достойны сравнения с теорией относительности.);

и разрешает агрегирование и делегирование (Aggregation and Delegation), читайте последний содержательный раздел, тут:

https://learn.microsoft.com/en-us/windows/win32/directshow/how-iunknown-works

Я вам очень благодарен что вы дали мне повод вспомнить-найти эти ссылки, и привести их здесь. Именно вот эта последняя ссылка содержит то, что мне позволило окончательно осознать базовую идею больше 15 лет назад, такое не забывается.

Для того, чтобы работало агрегирование, необходимо:

  1. Специальным образом создавать класс. Обратите внимание на параметр pUnkOuter у метода CreateInstance интерфейса IClassFactory. Ваш DirectShow поддерживает агрегацию?

  2. Агрегация компонентов должна быть прозрачной для вызывающей стороны. Следовательно, агрегат должен предоставлять единый IUnknown интерфейс, при этом агрегированный компонент зависит от реализации внешнего компонента. В противном случае вызывающий увидел бы два разных IUnknown в одном и том же агрегате. Так как в C++ двойное наследование невозможно, то приходится придумывать пути с переименованием IUnknown в делегирующую и неделегирующую версию.

Как видно, для агрегации совершенно недостаточно возвращать интерфейс внутреннего объекта.

Эта ситуация до косточек разобрана в книге Дейла Роджерсона «Основы COM», глава 8 «Повторная применимость компонентов: включение и агрегирование».

Так как в C++ двойное наследование невозможно

Насколько я помню множественное наследование в С++ было разрешено, то есть ограничения накладывают именно правила для СОМ объектов которые вы излагаете, совершенно справедливо.

К сожалению (было очень интересно как все удачно складывается и работает), я уже много лет не разрабатываю внутренности DirectShow фильтров, поэтому у меня действительно нет (теперь) совершенно наглядного примера проилюстрировать то что я пытался донести.

Я проверю в указанной вами книге Дейла Роджерсона, возможно оно поможет мне вспомнить пример реализации, который привел меня к тому пониманию которое я пытался изложить.

Для того, чтобы работало агрегирование, необходимо:

...

то приходится придумывать пути с переименованием IUnknown в делегирующую и неделегирующую версию

можно считать что вы согласились хотя бы с тем, что делегирование возможно?

Насколько я помню множественное наследование в С++ было разрешено

Двойное наследование — это когда от одного и того же интерфейса дважды наследуешься. В C++ это невозможно. Поэтому приходится переименовывать IUnknown, чтобы иметь две VTable с одинаковыми методами.

можно считать что вы согласились хотя бы с тем, что делегирование возможно?

В описанном вами варианте — нет. Вы пишете, что в QueryInterface можно возвращать какой хочу интерфейс, это не так. Вы можете возвращать только те интерфейсы, от которых унаследованы, а если хотите возвращать чужие интерфейсы, то вам необходимо создать агрегат, о чём в вашей статье не сказано.

в QueryInterface можно возвращать какой хочу интерфейс, это не так.

Меня почему-то не оставляет ощущение что вы не довольны именно тем, что кто-то кроме вас знает «тайну», кто-то посмел рассуждать на запрещенную тему.

Но вы мне действительно помогли закрыть мой пробел в теории, спасибо вам!

Эта ситуация до косточек разобрана в книге Дейла Роджерсона «Основы COM», глава 8

Да! Это та теория, которой мне не хватало! И это то, с чем я очень плотно работал практически, в DirectShow. В книге это с подробными пояснениями и объяснениями, да еще и в прекрасном переводе (откуда он взялся?).

Единственное он там пишет в подразделе «Сравнение включения и агрегирования» что:

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

Но вот в DirectShow по моим ощущениям это соотношение один к одному, если вообще не обратное!

Ну и там вроде не написано, что делегирование обеспечивает «самый эффективный способ взаимодействия объектов» (вы наверно забыли название статьи в пылу дискуссии), по крайней мере явно не написано.

Дело в том, что при кодировании-декодировании видео важна эффективность – FPS-ы. Мы внутри этих С++ фильтров даже использовали функции, написанные на ассемблере с использованием MMX, SSE расширений процессора, в том числе. Не говоря уже о разных схемах организации потоков в зависимости от количества доступных процессоров в системе.

Он слишком общий или слишком простой чтобы его выделить и описать как отдельный шаблон проектирования. 

А наследование - делегирование реализации части функциональности классу-предку, CRTP или абстрактные методы - делегирование потомку.

В общем - всё делегирование, кроме пчёл.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации