суббота, 2 ноября 2013 г.

Ещё раз про Supports

Навеяно вот этим - http://18delphi.blogspot.ru/2013/10/supports.html?showComment=1383223827244#c6258577626124847121

Почему-то МНОГИЕ (если не все) видят в моих постах какое-то "искание абсолюта" или "попытки быть гуру" и построить "коня в вакууме.

Вот ОТНЮДЬ не так.

Я не ищу абсолюта, и не пытаюсь добыть серебряную пулю или построить "сферичаского коня в вакууме".

Напротив!

Я - ПРАКТИК.

РЕМЕСЛЕННИК,

Без ВЫСШЕГО ОБРАЗОВАНИЯ.

Я не пытаюсь найти "универсальный рецепт".

Наоборот. Я ЛИЧНО считаю, что УНИВЕРСАЛЬНЫХ рецептов - НЕТ.

Я пишу лишь о том, с чем столкнулся НА ПРАКТИКЕ.

И я - НИКОГО НИКУДА не "тяну". И НИКОМУ НИЧЕГО - не навязываю.

Про Supports - я написал БАНАЛЬНУЮ вещь - "можно сделать так", а "можно сделать так".

И понятно, что решения - не РАВНОЗНАЧНЫ.

Одно - БОЛЕЕ ОБЩЕЕ, другое - БОЛЕЕ ЭФФЕКТИВНОЕ.

Ну по моей ПРАКТИКЕ.

Не более того.

Я отношусь с ГЛУБОКИМ ПРЕДУБЕЖДЕНИЕМ к Supports и QueryInterface.

Это - ПРАВДА.

И стараюсь их не применять "при прочих равных". Например при помощи такой "техники", как была описана по ссылке выше. А вообще говоря - техник много. Для РАЗНЫХ "частных случаев".

И это - продиктовано - моей ПРАКТИКОЙ. Не более того.

Просто был момент - когда появились интерфейсы, то все увидели - "о! интерфесы - круто".

И "УВЛЕКЛИСЬ" использованием интерфейсов вообще и Supports - в частности.

И я не был "счастливым исключением".

И горько поплатился за это. Временем жизни проведённым в отладке и под профайлером.

А "время жизни" - это - самое ЦЕННОЕ. (Люди "за 40-к" - меня наверное поймут)

Попробую привести пример.

В эпоху ПОВАЛЬНОГО УВЛЕЧЕНИЯ интерфейсами (когда все мы были молоди и казалось, что "море по колено") - Я САМ ЛИЧНО "родил" следующую ИДИОТСКУЮ конструкцию:

TmyObject = class(TPersistent)
 f_Owner : TPersistent;

 function GetOwner: TPersistent; override;
 begin
  Result := f_Owner;
 end;

 procedure SetOwner(anOwner : TPersistent);
 begin
  f_Owner := anOwner;
 end;
 
 function QueryInterface(anIntf : TGUID; out Obj): hResult;
 begin
  if Self.GetInterface(anItf, Obj) then
   Result := S_OK
  else
  if Supports(f_Owner, anIntf, Obj) then
   Result := S_OK
  else
   Result := E_NoInterface;
 end;
end;//TmyObject 

Что тут написано?

У объекта - БЫВАЕТ владелец.
Если у объекта запрашивают интерфейс, то мы сначала пытаемся получить интерфейс у объекта.
Если это НЕ ПОЛУЧИЛОСЬ, то пытаемся получить интерфейс у ВЛАДЕЛЬЦА.

И так - "по рекурсии".

Код конечно - ИДИОТСКИЙ. Я СЕЙЧАС - это - ПОНИМАЮ. Но не 15-ть лет назад скажем.

Если вы сразу скажете, почему он идиотский - я пожму вам руку. Мой вам "респект и уважуха". Можете дальше не читать "старпёра"...

Но "когда-то" мне казалось, что код - "очень даже правильный".

И что "делает, что должен".

Если не получили интерфейс у объекта, то пытаемся получить интерфейс у его владельца. "логично"...

И во-многих случаях - "РАБОТАЕТ".

Опустим те случаи, где при таком подходе отдавались "паразитные интерфейсы". Их было НЕМНОГО и для них имелись ОТДЕЛЬНЫЕ "костыли" и "заточки"...

А в ЦЕЛОМ - ВСЁ работало КАК НАДО.

КАК И ЗАДУМЫВАЛОСЬ...

Только ПОТОМ, я многие ЧАСЫ провёл под отладчиком и профайлером в поиске ответа на вопрос "что же ТАК всё тормозит".

А ВСЁ ОЧЕНЬ ПРОСТО - во-первых GetOwner - НЕ САМЫЙ быстрый метод (он - dynamic, а не virtual, если я не ошибаюсь), да и бог бы с ним...

ВЛОЖЕННОСТЬ объектов БЫЛА (да и есть) ДОСТАТОЧНО - глубокая.

А тут начинал играть роль другой фактор.

Спросили интерфейс у объекта. Он его не поддерживает. Тогда спросили у родителя. Он - тоже не поддерживает. Тогда у следующего родителя. Он - ТОЖЕ НЕ ПОДДЕРЖИВАЕТ. И так ПО ВСЕЙ ЦЕПОЧКЕ. И в итоге - ПРОБЕЖАЛИСЬ ПО ВСЕЙ ЦЕПОЧКЕ. Сделали МАССУ вычислений. А получили nil и E-NoInterface. Что и следовало ожидать.

А таких Supports и QueryInterface - МАССА. Всяких разных. Для РАЗНЫХ ЦЕЛЕЙ.

На самом деле.

И КАЖДЫЙ делает ВЫЧИСЛЕНИЯ, которые "на поверку" - НЕ НУЖНЫ.

В конечном итоге профайлер и отладчик показали мне всё это и я ИЗБАВИЛСЯ от ЭТОГО УЖАСА.

Но это заняло "много ценного времени моей жизни". Которое я мог бы потратить на что-тто другое.

Может быть - я - БОЛВАН и ИДИОТ, что сотворил такой АД и УЖАС. Может быть - вы УМНЕЕ меня и вам повезло.

Но только "подобный" АД и УЖАС - я наблюдал у разных других разработчиков. Под "разными соусами".

Так что - я тут "явно не одинок".

Если вы умнее "нашей банды" ИДИОТОВ и БОЛВАНОВ - вам - повезло :-)

Но к Supports и QueryInterface я ЛИЧНО - "отношусь с предубеждением".

Если хотите ЕЩЁ примеров - пишите. Приведу - ЕЩЁ.

И не только из СВОЕГО ЛИЧНОГО опыта.

А так...

Я никого "никуда не тяну", НЕ ПОУЧАЮ и не "стараюсь помогать"...

Просто хочется ПОКАЗАТЬ ОШИБКИ, которые я САМ ЛИЧНО СДЕЛАЛ. Чтобы предостеречь ДРУГИХ людей от этих ошибок.

"Умные учатся на чужих ошибках, а дураки - на своих"...

Хотя на Руси - обычно это не верно....

P.S. Пора переименовывать блог в "записки старпёра"... Оно так вернее суть отражает.. Да и люди может быть будут относиться со "снисхождением".. Чего уж с убогого взять...

4 комментария:

  1. Странные вещи пишите Александр... Очень странные...
    <-- Документация System.SysUtils.Supports
    Indicates whether a given object or interface supports a specified interface.

    Call Supports to determine whether the object or interface specified by Instance, or the class specified by AClass, supports the interface identified by the IID parameter. If Instance supports the interface, Supports returns the interface as the Intf parameter and returns True. If AClass supports the interface, Supports does not return an interface, but still returns True. If the interface specified by IID is not supported, Supports returns False.
    -->
    Обратите внимание на упоминание в документации слов objects, class, instance.
    Из документации явно следует, что SysUtils.Supports предназначена (задумывалась авторами) для проверки, поддерживает ли данный класс или объект (экземпляр класса) данный интерфейс. Иными словами: умеет ли *данный объект* «делать это»? Обратите внимание — объект, а не кто-то, кто как-то связан с этим объектом или кого можно получить, зная объект.
    В приведённом Вами примере, в сущности, это правило переопределяется, включая в рассмотрение другие объекты, включённые в цепочку Owner, по рекурсивному алгоритму.
    Для меня это выглядит, по меньшей мере странно, поскольку существенно размывает логику, описанную в документации, и чревато артефактами вида: в цепочке Owner может быть более одного объекта, поддерживающего запрашиваемый интерфейс, и совершенно не факт, что мы, поднимаясь по цепочке Owner, наткнёмся сначала на нужный.
    Что касается производительности, то и тут нечему удивляться, ведь для кода, использующего Supports, количество объектов, задействованных в обработке наперёд неизвестно.
    Резюме: в снижении производительности виноваты не Supports/QueryInterface, а их использование не в соответствии с их назначением.
    Ну и вообще, если нечто делается очень много и очень часто, то в этом, IMHO нужно очень осторожно применять алгоритмы, использующий прямой перебор. QueryInterface, как раз, один из таких алгоритмов.

    Что ещё бросилось в глаза в Вашем примере. Вы наследуете TmyObject от TPersistent, который является достаточно низкоуровневым классом. Ожидать появление поведения TmyObject.QueryInterface в других классах можно, если эти классы либо унаследованы от TmyObject, либо к ним «подмешана» функциональность TmyObject посредством mix-in. Второе я упоминаю, поскольку как я понял, Вы часто используете эту технику.
    Так вот, первый вариант мне представляется весьма маловероятным, поскольку TmyObject не функционален, и делать его базовым классом смысла я не вижу никакого. Тем более, для какого класса сущностей это делать? Правда, возможно, Вы привели крайне малую часть TmyObject и действительно, использовали его на нижнем уровне модели.
    Второй вариант (подмешивание, например, к потомкам TControl) чреват серьёзным усложнением, логическими артефактами, поскольку появляются дополнительные владельцы кроме Owner из TComponent и Parent из TControl, и этим попросту опасен.
    Правда, есть ещё третий путь: ваши шаблоны кодогенерации, которые способствуют дублированию бизнес-логики в местах, где Вы находите уместными её применение.
    Собственно, хотелось бы понять вот что: как Вы добились, что функциональность, помещённая в TmyObject.QueryInterface, проявилась в таком количестве мест, что это сказалось на производительности?

    ОтветитьУдалить
    Ответы
    1. Спасибо добрый человек.
      Научил уму-разуму.

      А я то сижу и думаю - "куда этот Supports ещё засунуть".

      Удалить
    2. "Правда, есть ещё третий путь: ваши шаблоны кодогенерации, которые способствуют дублированию бизнес-логики в местах, где Вы находите уместными её применение."

      Про "дублирование" я вам НЕ РАЗ уже объяснял. Если вы не поняли, то это - не мои проблемы. Прошу этой темы больше не касаться.

      Удалить
    3. "Собственно, хотелось бы понять вот что: как Вы добились, что функциональность, помещённая в TmyObject.QueryInterface, проявилась в таком количестве мест, что это сказалось на производительности?"

      Потому, что я ИДИОТ - я же ЯВНО написал. РАДЫ? САМОВЫРАЗИЛИСЬ? СПАСИБО!

      Удалить