среда, 12 марта 2014 г.

Наследование... Полиморфизм..

http://blogs.embarcadero.com/vsevolodleonov/2013/02/07/alex_reply_friendship/

Знаете что.. Вот не согласен я с Леоновым...

Скорее согласен с Алексеевым...

Особенно про "интерфейсы"...

Особенно если "вспомнить" про "примеси и АОП.

НЕТУ "чистого" дерева наследования. В СЛОЖНЫХ системах.

Я вот СЕЙЧАС одну такую "СЛОЖНУЮ" систему перебираю по винтикам и болтикам.

И там - чем дальше - тем больше возникают "боковые" ветки наследования.

Читай - "примеси" и АОП.

Я бы привёл пример. Но боюсь, что для "читающей публики" он слишком сложен.

А уж у "сокрытия" - ЕСТЬ ГЛУБОКИЕ КОРНИ.

Ещё про "аспекты"...

Задам несколько вопросов...

Автомобиль ИНКАПСУЛИРЕТ (агрегирует) колёса или наследуется от телеги?

Велосипед с автомобилем в КАКИХ отношениях?

А самолёт? Он агрегирует шасси или наследуется от телеги? Или от автомобиля? Или от велосипеда?

А винт? Самолёт наследует? Или агрегирует? Если наследует, то от кого?

А вот скажет корабль? Он агрегирует винт или наследует? От кого? От самолёта?

А вот реактивный самолёт? Он НАСЛЕДУЕТСЯ от самолёта "с винтом"?

А как же корабль?

А или реактивный самолёт? Он наследуется от ракеты? Или ракета наследуется от него?

А вот корабль-"ракета" он в каких отношениях с самолётом и ракетой?

А вот колёсный пароход? Он наследуется от корабля? Или корабль от него наследуется? Или колёсный пароход наследуется от телеги? Или от велосипеда? Или от автомобиля?

А реактивный автомобиль (такие тоже бывают) он от кого наследуется? От ракеты? Или от реактивного самолёта? Или от телеги? Или от корабля-"ракеты"?

Не устали ещё от вопросов?

Они ведь ТРИВИАЛЬНЫЕ :-)

И ТАКИХ вопросов - у МЕНЯ ещё МИЛЛИОН...

Скажу одно... ПРИМЕСИ.

Ещё РАЗ - ПРИМЕСИ и АОП.

Колёса, мотор, и т.д. и т.п. это НЕ НАСЛЕДОВАНИЕ и не "прямая" агрегация.. Это - АСПЕКТЫ. Это - АОП.

А насчёт "сокрытия".... Напишу ещё ВОТ ЧТО...

Есть РАЗНЫЕ категории ПОЛЬЗОВАТЕЛЕЙ Классов (механизмов)... Есть ТУПОЙ ПОЛЬЗОВАТЕЛЬ.. Есть профессиональный водитель.. Есть механик.. Есть ремонтник.. Есть человек, который ПРОИЗВОДИТ механизм... Или ещё работник на мойке.. Или заправщик на заправке...

Кому-то нужен доступ к колёсам.. Кому-то к мотору.. Кому-то к багажнику.. Кому-то к бачку омывателя.. Кому-то к бензобаку.. Кому-то ко всему из вышеперечисленного.. Кому-то к какому-то подмножеству...

Так что вопрос - вроде "простой", но СОВСЕМ НЕ ОЧЕВИДНЫЙ...

Friend скажете вы.. Отчасти.. Отвечу я... К Friend - есть МНОЖЕСТВО вопросов..

ПЕРВЫЙ - это то, что Friend указывается в БАЗОВОМ классе, а не во Friend'е... Если эту зависимость нарисовать на UML, То СРАЗУ ВИДНО, что "направление зависимости" - НЕПРАВИЛЬНОЕ... Friend НАДО указывать в САМОМ Friend'е, а не в базовом классе...

И это - ТОЛЬКО ОДИН ВОПРОС...

А их - МАССА...

Тема тут явно НЕДОРАБОТАНА...

Правда Вирт пытается её развивать в Модуле и Обероне... Если я правильно понимаю...

И вот тут "в вопросе сокрытия" - возникает "мостик" к ИНТЕРФЕЙСАМ (о чём и говорит Алексеев (если я его правильно понял)).

Есть TSomeComlexMechanism - собственно "механизм".. И "НАД НИМ" есть НЕСКОЛЬКО РАЗЛИЧНЫХ "интерфейсов", регламентирующих "уровни доступа" - ISomeSimpleMechanism, ISomeComplexMechanisn, IWheels, IEngine, IDoor, IDoorAndWheels, IGasolineTank, IMetrics, IDevices etc.

(Кстати эта тема "отчасти развита" в FM с их "сервисами")

Мысль понятна? (Я правда САМ делаю - ПО-ДРУГОМУ).

Если у "пользователя" нашим механизмом "хватило мозгов" получить соответствующий интерфейс например IEngine - то это ЗНАЧИТ (допускаю что так), что он ЗНАЕТ, что с ним ДЕЛАТЬ и КАК это ДЕЛАТЬ.

Примитивно - если у человека "хватил ума" снять крышку двигателя, то это значит, что он ЗНАЕТ, что с НИМ ДЕЛАТЬ.. Ну или он ПОЛНЫЙ ИДИОТ.. Такого - НЕ ИСКЛЮЧАЮ...

Посему - ещё раз... НЕТУ "общего дерева наследования" и "строгой иерархии" машин и механизмов.

ЕСТЬ "множество аспектов применения и реализации".

35 комментариев:

  1. Думаю, основная неприятность с наследованием состоит в подсознательно неверном его позиционировании.
    Для меня (и не только) наследование - суть, специализация, что бы там ни говорил Гради Буч (хотя, м.б. он и передумал с тех пор...) Если смотреть с этих позиций ситуация во многом проясняется. Ведь специализация подразумевает, что у потомка не может быть избыточных свойств.
    В частности, ни в одном из вопросов я не увидел специализации, соответственно и наследования.

    Сокрытие? Концепция непрерывного рефакторинга указывает, что скрывать нужно по максимуму, а открывать - по необходимости. Лучше скрыть, а потом открыть, если в этом возникла необходимость, чем столкнуться с усложнением и нестабильностью, вызванными нарушением инкапсуляции и привязкой к особенностям реализации.
    Открывать же тоже можно по разному. Лучше всего - в самом абстрактном виде - через интерфейсы, опять же, скрывая детали по максимуму. Если это возможно, конечно.

    АОП? Эта тема весьма интересна. Но только как идея. Как концепция выделения сквозной функциональности.
    Мне не очень нравится терминология АОП, мне кажется она уводит от сути. А суть же состоит в выражении алгоритмов посредством протоколов, где протоколом является последовательность событий, рассылаемых в известные объекты в известной последовательности. Кроме того, должна быть стандартизована схема установки обработчиков на эти события и возможность PassEvent в обработчиках (возможность вызова обработчиков, установленных *перед* текущим (ранее текущего): без этого - никуда.

    Примеси? Я кажется начал понимать, почему эта тема пользуется популярностью в определённых кругах.
    Для себя я обозначил область применения этой техники. По мне - она выглядит тяжеловесно, и ведёт к запутыванию кода, если применяется часто. На мой взгляд, это во многом уступает предыдущему, хотя в некоторых случаях (когда нужно выполнить идентичную специализацию нескольких классов) - реально приходит на помощь. Но в моей практике такое случается очень редко, чаще всего, вследствие груза "навязанной" архитектуры (VCL например), так что и применение этого подхода весьма ограничено.

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

    ОтветитьУдалить
    Ответы
    1. Я знал, что Вы отреагируете.. Но не думал, что так быстро :-)

      ВО МНОГОМ с вами согласен :-)

      "Новых парадигм" я кстати не ищу :-)

      Я как-то в рамках текущих умещаюсь...

      АОП и "примеси" - ничего тяжеловесного я не вижу...

      "Я думаю, мог бы кое-что предложить, но нужно сказать столько слов..." - вот ТУТ - жаль.. я бы - С УДОВОЛЬСТВИЕМ вас бы почитал.. Я давно уже понял, что Вам ЕСТЬ ЧТО сказать..

      Ну и процитирую:
      "Концепция непрерывного рефакторинга указывает, что скрывать нужно по максимуму, а открывать - по необходимости. Лучше скрыть, а потом открыть, если в этом возникла необходимость, чем столкнуться с усложнением и нестабильностью, вызванными нарушением инкапсуляции и привязкой к особенностям реализации.
      Открывать же тоже можно по разному. Лучше всего - в самом абстрактном виде - через интерфейсы, опять же, скрывая детали по максимуму. Если это возможно, конечно"
      -- вот ТУТ - я ПОЛНОСТЬЮ согласен.

      Удалить
    2. "Я кажется начал понимать, почему эта тема пользуется популярностью в определённых кругах."
      -- почему, если не секрет? Каков Ваш взгляд?

      Удалить
    3. "По мне - она выглядит тяжеловесно, и ведёт к запутыванию кода, если применяется часто."
      -- "тяжеловесно" это в Delphi. Но есть языки (и вы их знаете), где такой тяжеловесности - нет.

      Хотя и в Delphi с применением UML и кодогенерации - тоже нет. Хотя Вы их и не любите :-)

      Удалить
    4. "Но в моей практике такое случается очень редко, чаще всего, вследствие груза "навязанной" архитектуры (VCL например)"
      -- к VCL "вообще" и к TPersistent и Assign - у меня есть МНОЖЕСТВО вопросов. Я бы их ПОКРИТИКОВАЛ бы. Я давно уже понял, что Borland "завёл не туда". Для СЕБЯ ЛИЧНО. Но пока - не знаю с чего начать..

      Но тема - глубокая и обширная...

      Удалить
    5. Этот комментарий был удален автором.

      Удалить
  2. «АОП и "примеси" - ничего тяжеловесного я не вижу...»
    -- Как-то я спросил Учителя: "Что можно сделать на Turbo Vision такого, чего нельзя на Object Professional?". Он мне ответил: "Ничего". Тогда я спросил, "Почему же Вы готовы отбросить наши наработки и почему выбрали Turbo Vision?". Он ответил: "Потому, что Turbo Vision - лучше".
    Просто вспомнилось...
    Ruby и Python. Могут - наверное, одинаково. В Ruby очень любят примеси, в Python... В них просто нет необходимости. Их нетрудно делать, но почему-то... Это просто ненужно.
    Мы *никогда* не использовали примеси. Но я не говорю, что это плохо. Я просто знаю, как без них обходиться, а в случае, когда для меня они уместны, вижу изъяны в архитектуре, которые делают их применение желательным. "Знаю как обходиться" - это не "знаю, как выкрутиться". Есть другой способ расширения функциональности, я его описал вкратце выше. Этот способ работает не на уровне статичных классов, но на уровне динамически создаваемых объектов. Он гибче и, на мой взгляд, позволяет получить более простое и изящное решение.
    Для меня примеси - это "предметная магия", лишняя сущность, если хотите. Из серии лишних сущностей, что не стоит множить сверх необходимости. И повторюсь, для меня существует типаж ситуаций (я его обозначил), в которых примеси - лучшее решение. И ещё повторюсь: это только для меня. Я ничего не утверждаю. Вообще: "Я не могу принять сторону - я не знаю никого, кто не прав" (c) БГ

    ОтветитьУдалить
    Ответы
    1. «"Я думаю, мог бы кое-что предложить, но нужно сказать столько слов..." - вот ТУТ - жаль.. я бы - С УДОВОЛЬСТВИЕМ вас бы почитал..»
      -- Тут есть одна проблема. Точнее - ПРОБЛЕМА.
      Я не могу найти простого и наглядного примера, на котором, не утомив рассуждениями (хотя уже думаю, что проще их опустить) проиллюстрировать преимущества расширенного делегирования.
      У всех простых примеров есть один порок. Их можно реализовать очень по-разному, а любой человек на подсознательном уровне будет считать *правильным* решением - привычное.
      То, о чём я хотел рассказать - непривычно. Для Delphi - уж точно. Причём, в техническом аспекте там не так уж много нового. В конце концов, несколько обработчиков на событие можно делать и в Java, и в Qt, и в Objective C. Тема PassEvent правда, часто не раскрыта, а без него динамического множественного наследования не получится, или получится, но это не будет наследованием. А это - выплеснуть вместе с грязной водой сами знаете кого.
      Наконец, даже если я изложу всю технику, возникнет резонный вопрос: "А зачем всё это?". - Мне придётся ставить более сложную задачу, но она опять же, будет вырвана из контекста, и опять же, существует масса "привычных" способов сделать "это"...
      И к тому же, нужно править System. И компилировать его с ключом "-y", о котором мало кто знает, потому, что это никому не нужно :-) И "огрести" артефакты при использовании IDE Delphi (System-то свой, а раз так, то и всё остальное - тоже). А без правки - не поймать момент, когда объект ещё жив, но точно известно, что после эжтого момента его начнут разрушать. Момент, предшествующий BeforeDestruction. А это - часть основной идеи. Без этого можно, но будет уже не то...
      Наверное, нужно просто взять и начать писать. В конце концов, есть материалы, которые можно доработать.
      Да, там есть ещё о чём рассказать... :-)
      Помню, меня позабавило, когда Всеволод мне как-то "указал" на построение форм по метаданным... А что, если я скажу, что у нас *все* без исключения формы строятся на основе метаданных, и большинство - только по ним, автоматически. Что поля-внешние ключи автоматически преобразуются в поля выбора из справочников/сущностей, что запросы к БД пользователь может "рисовать" сам, посредством инструмента, который проще QBE? Вот здесь и DDD, которым, как я понимаю, Вы интересуетесь.

      Удалить
    2. «"Я кажется начал понимать, почему эта тема пользуется популярностью в определённых кругах."
      -- почему, если не секрет? Каков Ваш взгляд?»
      -- Начиная с марта (если не ошибаюсь), я регулярно читал Ваш блог. Началось с того, что я прочитал заинтриговавшую статью Всеволода, где было интервью с Вами и с кем-то из Ваших коллег, который потом написал весьма удачную статью в Вашем блоге, посвящённую методике применения UML в Вашей компании.
      Несколько месяцев назад я пришёл к выводу, что Вы с коллегами нашли способ борьбы со сложностью, применяя модели в явном виде, разработав и доведя "до ума" (ну, по крайней мере, для себя) инструменты, позволяющие выполнять переход от моделей к коду.
      Примеси функционально (но не концептуально) во многом подобны тому методу которому следую я.
      Выражаясь более определённо, они (с рядом оговорок) функционально подходят для выражения их средствами сквозной функциональности. Такую функциональность можно оформить в виде шаблонов и многократно использовать в различных контекстах.
      Скажу честно, лично я не рискнул бы это делать без инструмента, который позволил бы не запутаться, имея дело *только с кодом*. У Вас с коллегами такой инструмент есть. Возможно, примеси стали для вас ключом к идеям АОП - я обратил внимание, что Вы часто ставите примеси и АОП рядом.
      Хотя вероятно, я ошибаюсь. Я ведь очень мало знаю о том, как Вы с коллегами работаете...

      Удалить
    3. «"По мне - она выглядит тяжеловесно, и ведёт к запутыванию кода, если применяется часто."
      -- "тяжеловесно" это в Delphi. Но есть языки (и вы их знаете), где такой тяжеловесности - нет.

      Хотя и в Delphi с применением UML и кодогенерации - тоже нет. Хотя Вы их и не любите :-)»
      -- Но мы-то программируем на Delphi...
      И потом, я конечно же знаю языки, где примеси выглядят естественно, Ruby тот же, но я не могу забыть ощущений, с которыми обнаруживал в классах Ruby функциональность, вносимую примесями - эти ощущения совершенно не соответствовали принципу наименьшего удивления...
      Насчёт UML и кодогенерации я с Вами спорить не стану. Ваш подход работает и если Вы не ощущаете дискомфорта в применении примесей, значит а) Вы готовы, что Вы или коллеги их примените в *модели* и б) Ваши инструменты достаточно совершенны для того, чтобы применение примесей не создавало неудобств.
      И кстати... Не говорите, что я не люблю UML и кодогенерацию. В Университете я читаю курс, неотъемлемой частью которого является UML. Поскольку работа в ВУЗе не несёт для меня вообще никакого материального интереса - это просто хобби, моя "нелюбовь" к UML была бы просто диагнозом. Ну это просто мазохизм какой-то, выбирать себе хобби, сопряжённое с нелюбимыми вещами :-) Я люблю UML, я считаю его удобным способом коммуникации, но Александр, мне приходится всерьёз искать инструменты для хотя бы рисования UML-диаграмм! Сейчас в "фаворе" Eclipse+ Papyrus. Меня бы уволили за такую "юзабельность" как там, если бы речь шла о наших приложениях, но функционально - IMHO самый проработанный вариант из бесплатных.
      Что касается кодогенерации, то как я могу её не любить, если DDL для наших баз данных на 98% генерируется по метаданным, представленным в реляционной форме и сопровождаемым нашими аналитиками? А в том DDL-коде, что всё-таки пишется "вручную", есть вставки кода на Python для генерации итогового DDL. Помните моё письмо по поводу шаблонов Genshi, в которых используется Python? Оно было написано под впечатлением успешного применения этой техники... Вообще, странно не любить то, что сам придумал, спроектировал и контролировал реализацию...

      Удалить
    4. "Я не могу найти простого и наглядного примера, на котором, не утомив рассуждениями (хотя уже думаю, что проще их опустить) проиллюстрировать преимущества расширенного делегирования.
      У всех простых примеров есть один порок. Их можно реализовать очень по-разному, а любой человек на подсознательном уровне будет считать *правильным* решением - привычное."

      -- именно ПОЭТОМУ я так и не смог "донести" своих мыслей.. Потому, что НЕ ПРОСТОГО ПРИМЕРА и "простые примеры" - НЕ ПОКАЗАТЕЛЬНЫ.

      Удалить
    5. «"Но в моей практике такое случается очень редко, чаще всего, вследствие груза "навязанной" архитектуры (VCL например)"
      -- к VCL "вообще" и к TPersistent и Assign - у меня есть МНОЖЕСТВО вопросов. Я бы их ПОКРИТИКОВАЛ бы. Я давно уже понял, что Borland "завёл не туда". Для СЕБЯ ЛИЧНО. Но пока - не знаю с чего начать..»
      – Мнда... Забавно.
      IMHO лишний раз демонстрирует, насколько разнятся наши подходы.
      У меня с коллегами TPersistent и Assign да и сам VCL в целом (как и FM) особенных вопросов не вызывают. Они «где-то далеко». Они сами по себе - мы сами по себе. Мы их создаём, используем, но очень редко наследуем. А если и наследуем, то на одного-двух потомков. Например, практически все формы - класса TForm. Фреймы практически все - TFrame, а не потомки. Наследование - редкость, используется только для специализации. Отработка бизнесс-логики - это не специализация какого-либо класса VCL.
      Не уверен, что Assign где-то у нас используется вообще, от TPersistent, понятно, «далеко не уйдёшь», но поскольку почти вся сериализация-десериализация построена на фабриках, да и сам механизм загрузки представлен протоколом в том смысле, о котором я говорил выше, каких-то проблем я не вижу.
      Вообще же, подход, основанный на вынесении сквозной функциональности в протокол и обработчики, даёт большую свободу в работе с классами. Их роль снижается, на первое место выходят объекты, которые почти всегда являются наследниками TSBaseClass – прямого потомка TObject. Именно с х помощью отрабатываются все разновидности ассоциаций - собственно ассоциация, агрегирование, композиция... TSBaseClass потому, что именно на его уровне поддержан механизм, о котором я говорил. Хорошо было бы это сделать вообще в TObject, но System – магический модуль... :-)
      Но Ваши наблюдения (относительно TPersistent и Assign) были бы весьма интересны, хотя бы, в форме тезисов.
      Возвращаясь к проблемам, то место, где архитектурные решения VCL действительно создают для нас сложности — это God Object TDataSet. Вот тут — да. Тут без примесей — только m4 :-) Но примеси — проще и естественней. Особенно - в Delphi.

      Удалить
  3. "И компилировать его с ключом "-y", о котором мало кто знает, потому, что это никому не нужно :-)"

    -- я знаю :-)

    ОтветитьУдалить
  4. "Помню, меня позабавило, когда Всеволод мне как-то "указал" на построение форм по метаданным... А что, если я скажу, что у нас *все* без исключения формы строятся на основе метаданных, и большинство - только по ним, автоматически. Что поля-внешние ключи автоматически преобразуются в поля выбора из справочников/сущностей, что запросы к БД пользователь может "рисовать" сам, посредством инструмента, который проще QBE? Вот здесь и DDD, которым, как я понимаю, Вы интересуетесь."

    :-) знакомо :-)

    ОтветитьУдалить
  5. 2 Namerec.

    При всём моём безграничном УВАЖЕНИИ к вам.

    Вы "на самом деле" не "так просты", как хотите казаться.

    Похоже, что вы "противник велосипедов", но ПРИ ЭТОМ вы САМИ "изобрели множество велосипедов". :-)

    Я ДАВНО заметил ЭТО за собой - ЧЕМ БОЛЬШЕ ты делаешь на какой-то "почве", тем БОЛЕЕ КРИТИЧЕСКИ ты относишься к тому, что ДРУГИЕ делают на той же самой "почве".

    Что по сути?

    Вот ОЧЕНЬ ЖАЛЬ, что Вы не раскрываете СВОЕГО КОДА.

    Хотя бы части его.

    Вы зря "боитесь" или "опасаетесь".

    НИКТО ничего не украдёт.

    Что скажу до того "как я Вас понял".

    Хотя Вы и "противник примесей", но Вы ИХ ИСПОЛЬЗУЕТЕ.

    Если я правильно понял.

    Вы создаёте объект в run-time и потом добавляете к нему делегатов, обеспечивающих те или иные аспекты.

    Я же добавляю аспекты в compile-time.

    Вот и вся разница?

    Я прав?

    Я правильно Вас понял?

    ОтветитьУдалить
  6. Я бы посмотрел на ваш TSBaseClass хотя бы "одним глазком"..

    Можно и не публично? :-)

    Там у Вас наверное что-то вроде Duck-Typing Python'а. Или "альтернатива RTTI". Некая "мапа" "имя метода"-"его делегат".

    И на основе "делегатов" Вы "примешиваете" функциональность к объектам.

    Или я СОВСЕМ заблуждаюсь?

    ОтветитьУдалить
  7. «Вот ОЧЕНЬ ЖАЛЬ, что Вы не раскрываете СВОЕГО КОДА.»
    -- Я не последний человек в фирме, многое сделал сам, но не я принимаю решения, что открывать, а что - нет.
    Тем не менее, вероятнее всего, я очень скоро это сделаю.
    Принципиальное одобрение директора уже есть, осталось уладить технические детали.
    Думаю - на этой неделе.

    ОтветитьУдалить
  8. Хотя Вы и "противник примесей", но Вы ИХ ИСПОЛЬЗУЕТЕ.»
    -- Алексндр, давайте расставим точки над i.
    1. Я *не* противник примесей - это технический приём с определённой областью применимости. Я её обозначил.
    Использовать примеси более широко мы не видим необходимости и (в дополнение) видим опасность в увеличении сложности. Возможно, наличие инструментов, подобных Вашим, усложнение нивелирует.
    2. Примеси и расширенное делегирование - параллельные вещи. Можно использовать и то и другое одновременно - это не противоречит решительно ничему.
    Примешивание определено на классах, ввиду этого - статично. Вы *не сможете расширить* примесями функциональность класса в runtime, например, в зависимости от структуры таблицы, с которой объекту этого класса потребовалось работать в данный момент.
    Расширенное делегирование определено на объектах и событиях. Оно динамическое по природе. "Каркас", протокол, реализует общая функциональность, расположенную на определённом уровне фреймворка. Каркас определяется набором событий, происходящих в определённых объектах, в определённой последовательности.
    "Определённой" - значит, оговоренной, описанной (нарисованной на диаграмме последовательности, например), известной разработчику, который имеет право использовать это знание.
    Протокол работы с объектами, реализующими каркас, подразумевает событие подготовки объекта, рассылаемое «в приложение» (очень грубо, поскольку м.б. в другой объект, в соединение с БД, например). В качестве параметра в это событие передаётся настраиваемый объект и, в ряде случаев, дополнительный контекст, опираясь на который в обработчиках события подготовки можно выполнить настройку.
    Настройка подразумевает создание объектов-обработчиков, обрабатывающих события каркаса. Эти объекты часто имеют смысл только в контексте объекта (или объектов), определяющих каркас и не имеют смысла за его (контекста) рамками. Поэтому, чаще всего жизненный цикл этих объектов определяется временем жизни объектов, определяющих каркас.
    Объекты-обработчики, жизненный цикл которых определяется объектами каркаса (они тоже могут быть объектами-обработчиками) называют симбиотами (или, жаргонно - «паразитами»).

    ОтветитьУдалить
  9. Симбиот может существовать в контексте *любого* объекта, причём сам объект может не знать о его существовании. Ну, просто разработчик счёл необходимым так определить жизненный цикл симбиота.
    Делается это одной строчкой, например, в конструкторе такого симбиота (статический вариант):
    InstallEventHandler(Instance, ev_Terminate, Self.CloseSelf);
    Или извне объекта-обработчика его можно сделать симбиотом:
    InstallEventHandler(Instance, ev_Terminate, handler.CloseSelf);
    Здесь:
    [InstallEventHandler]: *процедура*, выполняющая установку метода-обработчика на событие, происходящее в контексте объекта
    [Instance]: объекта, определяющий контекст — в общем случае, реализующий каркас или его часть.
    [ev_Terminate]: идентификатор *стандартного* события, происходящего ***до*** освобождения Instance, ***до*** вызова его метода BeforeDestruction. В момент рассылки этого события Instance ещё работоспособен, но принято решение о его освобождении — вызван метод Free.
    Для того, чтобы это сделать, требуются изменения в System. Нужно или Free изменить (вызвать CallBack, сохранённый в threadvar) или, что лучше, поправить _BeforeDestruction, например — так:
    <-- code --
    procedure dummy_BeforeDestruction(const Instance: TObject);
    begin
    end;

    var
    GLOBAL_BeforeDestruction: TBeforeDestructionProc = dummy_BeforeDestruction;

    procedure SetGLOBAL_BeforeDestruction(proc: TBeforeDestructionProc);
    begin
    if Assigned(proc) then
    GLOBAL_BeforeDestruction := proc;
    end;

    {$IFNDEF CPUX86}
    procedure _BeforeDestruction(const Instance: TObject; OuterMost: ShortInt);
    begin
    if OuterMost > 0 then
    begin
    GLOBAL_BeforeDestruction(Instance);
    Instance.BeforeDestruction;
    end;
    end;
    … … …
    -- code -->
    [Self, handler]: объект-обрабочик, который становится симбиотом на уровне класса (в конструкторе) или за его пределами, динамически.
    [CloseSelf]: метод, определённый в TSBaseClass. Реализация тривиальна: Self.Free.

    ОтветитьУдалить
  10. «Вы создаёте объект в run-time и потом добавляете к нему делегатов, обеспечивающих те или иные аспекты.»
    – Да.

    «Я же добавляю аспекты в compile-time.»
    – Ну, по крайней мере, мне так показалось :-) Вам виднее.

    «Вот и вся разница?»
    – Технически? - Возможно.
    Концептуально, для меня отличия крайне существенны.
    Динамический подход упрощает классы и подталкивает (стимулирует путём упрощения этой процедуры) к разделению функциональности по аспектам и повторному использованию.
    С наследования снимается «обязанность» по расширению функциональности — она переходит в обработчики.
    Приложение, в силу того, что обработчики реализующие аспекты, строятся динамически, становится адаптивным к данным, с которыми приходится работать, к их структуре, и другим вещам вещам, определяющим контекст.

    «Там у Вас наверное что-то вроде Duck-Typing Python'а. Или "альтернатива RTTI". Некая "мапа" "имя метода"-"его делегат".»
    – Да, там есть аналог map – словарь. Только не {«имя события»: «делегат»}, а {«имя события»: «список обработчиков (делегатов)». Обработчики (делегаты в Вашей терминологии) получают управления в порядке, обратном тому, в котором они были установлены. Последним управление получает обработчик по-умолчанию, который может быть реализован на уровне каркаса.
    Принципиальный момент, на котором я останавливался ранее. Рядом с InstallEventHandler определена процедура PassEvent(var AEvent: TEvent) предназначенная для применения исключительно в обработчиках. В случае её использования, управление будет передано всем обработчикам, установленным *до* (или ранее) текущего, после чего будет возвращено в текущий. Это даёт возможность обработчику-делегату получать управление как в пре-, так и в постпроцессе рассылки события, что делает ненужными события Before и After.

    ОтветитьУдалить
    Ответы
    1. Ответьте только "да" или "нет" - я Вас правильно понял? :-)

      Удалить
    2. «Ответьте только "да" или "нет" - я Вас правильно понял? :-)»
      -- Гм... Немного не понял вопрос.
      Отвечу, как понял. Мне показалось, что Вы понимаете излагаемое мною правильно.

      Удалить
  11. Ну вообще говоря - из всего вышенаписанного - я ПОНЯЛ, что и КАК вы делаете. Мне - ПОНЯТНО. :-) "интерес" так сказать - "удовлетворён". Скажем так - МНЕ НРАВИТСЯ... Это как у Теплякова... :-) Мне нравится, что он пишет, и мне нравится, что Вы делаете. Хотя я и делаю "по-другому".

    ОтветитьУдалить
    Ответы
    1. "Вы *не сможете расширить* примесями функциональность класса в runtime, например, в зависимости от структуры таблицы, с которой объекту этого класса потребовалось работать в данный момент."
      -- мы МОЖЕМ это сделать.. но не ПРИМЕСЯМИ.. а ДРУГИМИ инструментами..

      Удалить
    2. «мы МОЖЕМ это сделать.. но не ПРИМЕСЯМИ.. а ДРУГИМИ инструментами..»
      -- Александр, те только Вы с коллегами - все могут. И делают это.
      Есть паттерн Observer, в конце концов.
      Прелесть расширенного делегирования для меня в том, что этот подход унифицирует способ такого расширения, делает этот способ элементом архитектуры, доступный в единообразном виде везде и всегда.
      Унификация же выразительных средств и фиксация их номенклатуры упрощает проектирование и снижает порог входа, поскольку знакомый с пардигмой человек легко находит ответы на интересующие вопросы, используя только код.

      Удалить
    3. Ну да. И Observer в частности.

      Удалить
  12. Я только ОДНО не понял... ЗАЧЕМ ВАМ -y понадобился.. Это - "самое интересное".. Ну сделали свой "базовый класс", который поддерживает делегаты (это Ваш кстати термин), но зачем System.pas и TObject править?

    ОтветитьУдалить
    Ответы
    1. "– Да, там есть аналог map – словарь. Только не {«имя события»: «делегат»}, а {«имя события»: «список обработчиков (делегатов)». Обработчики (делегаты в Вашей терминологии) получают управления в порядке, обратном тому, в котором они были установлены. Последним управление получает обработчик по-умолчанию, который может быть реализован на уровне каркаса.
      Принципиальный момент, на котором я останавливался ранее. Рядом с InstallEventHandler определена процедура PassEvent(var AEvent: TEvent) предназначенная для применения исключительно в обработчиках. В случае её использования, управление будет передано всем обработчикам, установленным *до* (или ранее) текущего, после чего будет возвращено в текущий. Это даёт возможность обработчику-делегату получать управление как в пре-, так и в постпроцессе рассылки события, что делает ненужными события Before и After."
      -- лучше конечно "на код" посмотреть :-) особенно если "через неделю"..

      Удалить
    2. «Я только ОДНО не понял... ЗАЧЕМ ВАМ -y понадобился.. Это - "самое интересное".. Ну сделали свой "базовый класс", который поддерживает делегаты (это Ваш кстати термин), но зачем System.pas и TObject править?»
      -- ev_Terminate необходимо рассылать в момент, когда объект ещё работоспособен, но принято решение его освободить.
      BeforeDestruction для этого не подходит, поскольку это виртуальный метод, который может быть перекрыт в потомке. В некоторых классах перекрытие этого метода содержит действия по финализации объекта, и если рассылать ev_Terminate TSBaseClass.BeforeDestruction, то обработчик события будет работать с частично разрушенным каркасом.
      Рассылка ev_Terminate перед BeforeDestruction решает проблему, но нужно вносить изменения в System, чтобы это обеспечить.

      Удалить
    3. Не понял :-( Что мешает "закрыть" BeforeDestruction? Я писал как..

      Объявляем статический BeforeDestruction c reinroduce в вашем TSBaseObject. И всё..ни один наследник его не перекроет..

      Удалить
    4. «Не понял :-( Что мешает "закрыть" BeforeDestruction? Я писал как..»
      -- Сомневаюсь.
      Все классы в модуле Classes, Forms унаследованы от TSBaseClass, поскольку:
      <-- code --
      ... ... ...
      TPersistent = class(TSBaseClass)
      ... ... ...
      -- code -->
      Есть ряд сторонних библиотек, которые мы приобрели. Они при сборке фреймворка используют нашу версию Classes, Forms и прочего. В них ни о каком расширенном делегировании "ни сном ни духом". И там вполне могут перекрывать BeforeDestruction.
      Зачем мне нужно, чтобы никто не мог перекрыть BeforeDestruction, если разработчики сторонних библиотек сочли необходимым именно такой подход?
      Все должно работать так, как задумывалось авторами.
      А расширенное делегирование должно работать параллельно этому.

      Удалить
    5. Я понял.. Хотя я и не согласен с таким подходом.. Я лично предпочитаю править "базовые библиотеки" в случае КРАЙНЕЙ НЕОБХОДИМОСТИ.. тут я пока необходимость не увидел.. Хотя и понял...

      Удалить
    6. И... Я был бы счастлив, если бы не приходилось вносить изменения в System...

      Удалить
    7. «Я понял.. Хотя я и не согласен с таким подходом.. Я лично предпочитаю править "базовые библиотеки" в случае КРАЙНЕЙ НЕОБХОДИМОСТИ.. тут я пока необходимость не увидел.. Хотя и понял...»
      -- Ну... Мне показалось (и пока я не вижу альтернатив), что снабдить необходимой функциональностью все основные классы приложения, ограничившись минимальным внесением изменений в базовые модули - меньшее зло, чем постоянно делить для себя классы на "свои" (потомки TSBaseClass) и "чужие" (таковыми не являющиеся).
      Кроме того, наличие в базовых классах поддержки расширенного делегирования (далее Enhanced delegation, или ED) делает возможным оборачивание (wrapping) событий обычного делегирования в события расширенного, а также рассылку событий в базовых классах тогда, когда в этом есть потребность.
      Мы просто не смогли бы обеспечить рекурсивную загрузку компонентов рамках стандартной схемы, если бы не внесли в неё те минимальные изменения, в которых критически нуждались.
      Не смогли бы поддержать триггеры уровня приложения, если бы DataSet не поддерживал ED.
      В своё время рассматривалась масса разных вариантов, но все они были заметно более затратными и гораздо менее удобными.

      Удалить
    8. "очень познавательно" :-) как пишете Вы..
      На самом деле.

      Удалить