Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Все языки: Статьи, заготовки в FAQ > Обработчики событий без объекта


Автор: Fr0sT 14.03.12, 07:02
Все обработчики событий OnXXX имеют тип метода объекта (procedure / function of object). Однако не всегда есть объект, куда можно запихнуть эти обработчики. Что же делать? Заводить объект (и значит, писать Create и Free) только из-за этого?
Выход появился с введением в D2009 есть с применением методов класса. Они совместимы с методами объекта, а значит, пригодны для использования в качестве обработчиков.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    type
      TEventHandler = class
        class procedure ThreadTerminate(Sender: TObject);
      end;
     
    class procedure TEventHandler.ThreadTerminate(Sender: TObject);
    begin
      ...
    end;
     
    ...
     
    SomeThread.OnTerminate := TEventHandler.ThreadTerminate;


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

См. также шаманский метод leo в следующем посте.

Автор: leo 14.03.12, 07:21
Цитата Fr0sT @
Выход появился с введением в D2009 методов класса.

?!
Во-первых, методы класса существовали с незапамятных времен. Во-вторых, если в методе не используется Self, то создавать реальный объект вызовом Create совершенно не обязательно - можно юзать nil или через реальную переменную или просто приведением типа:
MyThread.OnTerminate:=TEventHandler(nil).ThreadTerminate;

Автор: Fr0sT 14.03.12, 07:34
Цитата leo @
Во-первых, методы класса существовали с незапамятных времен

Хм, да, действительно, даже в D7 есть. Значит, это я только в 2009-ых до них добрался))
Цитата leo @
MyThread.OnTerminate:=TEventHandler(nil).ThreadTerminate;

О, вот это тоже крутой метод, не знал!

Автор: leo 14.03.12, 08:01
Цитата Fr0sT @
Хм, да, действительно, даже в D7 есть

Даже в D3 есть ;), ведь у TObject практически все методы - классовые

Автор: --Ins-- 14.03.12, 09:17
Лучше все-таки завести объект и назначить обработчиком обычный instance-метод, чем связываться с грязными приемчиками. Управление временем жизни такого объекта не такая уж и проблема, тем более что это управление может быть не ручным, а автоматическим. Да и вообще, я считаю, что если в некой системе существует объект, генерирующий события, то странно что в этой системе нет места объекту, который по логике должен их обрабатывать - это уже не ООП, а какой-то гибрид.

Автор: Fr0sT 14.03.12, 10:42
--Ins--, ну, мой метод вовсе не грязен :) Кто ж виноват, что нет возможности просто и без запарок назначить обработчик, не засовывая его в класс.
Цитата --Ins-- @
если в некой системе существует объект, генерирующий события, то странно что в этой системе нет места объекту, который по логике должен их обрабатывать - это уже не ООП, а какой-то гибрид

Во-первых, ну и что? Гибрид так гибрид. Глобальные переменные и самостоятельные функции тоже уже не ООП, и кого это волнует.
Во-вторых, объекта может не быть чисто по структурным соображениям: к примеру, если есть сокет, у которого по OnConnect посылается запрос на сервер - то засовывать обработчик OnConnect в форму совершенно нелепо. Создавать экземпляр класса, который служит исключительно хранилищем обработчиков? Зачем плодить сущности, если можно обойтись без них.

Автор: --Ins-- 14.03.12, 11:01
Цитата Fr0sT @
--Ins--, ну, мой метод вовсе не грязен


Ну по сравнению с методом leo в целом да :)

Цитата Fr0sT @
Во-первых, ну и что? Гибрид так гибрид. Глобальные переменные и самостоятельные функции тоже уже не ООП, и кого это волнует.


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

Цитата Fr0sT @
Во-вторых, объекта может не быть чисто по структурным соображениям: к примеру, если есть сокет, у которого по OnConnect посылается запрос на сервер - то засовывать обработчик OnConnect в форму совершенно нелепо.


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

Автор: Fr0sT 14.03.12, 11:08
Это всё дело вкуса и каждого конкретного случая. Главное - чтоб была возможность выбора ;) В моём случае оказалось, что городить огород ради пары обработчиков, использующих только Sender - нецелесообразно, вот я и пошел другим путем.

Добавлено
Цитата --Ins-- @
В форму - совершенно нелепо. А в какой-нибудь объект-менеджер соединений

Это лепо, если сокетов много, а обработчики будут еще и на кучу других событий. Тогда уже не зазорно и класс-наследник сделать, либо действительно менеджер коннектов с внутренними методами и обработчиками. Однако в более простых случаях легче прилепить парочку автономных обработчиков и не создавать Hello world на основе паттернов

Автор: --Ins-- 14.03.12, 11:16
Цитата Fr0sT @
Это всё дело вкуса и каждого конкретного случая.


Скорее конкретного случая, чем вкуса. Для проектика который пишется за 2-3 дня и больше к его коду ты никогда не вернешься - так может и незачем действительно "разводить огород" - тут на размышление "а как бы лучше сделать" потратишь больше времени, чем если просто взять и сделать не задумываясь как-нибудь. Но в более сложном случае - лучше потратить чуть больше времени на декомпозицию, реализовать все в коде строго в соответствии с получившейся в результате декомпозиции моделью, и потом экономить множество времени уже на стадии сопровождения и расширения кода. И вкус тут ни причем

Добавлено
PS: Кстати, лично мне более интересен случай как наоборот - там где требуется процедура/функция использовать метод. А то всякие TList.Sort в этом случае портят малину <_<

Автор: leo 14.03.12, 12:02
Цитата --Ins-- @
Цитата Fr0sT @
--Ins--, ну, мой метод вовсе не грязен :)

Ну по сравнению с методом leo в целом да :)


А мой-то чем грязен, только тем, что непонятен "непосвященным"? ;) Грязный этот тот, который либо использует недокументированные возможности, либо может привести к непредсказуемым ошибкам из-за отключения проверки типов, передачи какого-то мусора и т.п. В данном же случае вполне "законно" вместо Self передается nil, что вполне допустимо, если этот параметр в обработчике не используется, а если используется, то получим вполне предсказуемый AccessViоlation ;)

Цитата Fr0sT @
О, вот это тоже крутой метод, не знал!

Для кого "крутой", а для кого "грязный" :) Была уже как-то подобная тема (может и не одна), там же (вроде как) рассматривался и вариант использования в качестве обработчика TNotifyEvent и обычной процедуры (о, боже, что на это скажет --Ins-- ;) )
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    type
      TNotifyProc = procedure(ProcParam:pointer; Sender:TObject);register;
     
    function NotifyProcAsEvent(Proc:TNotifyProc; ProcParam:pointer = nil):TNotifyEvent;
    begin
      with TMethod(Result) do
      begin
        Code:=@Proc;
        Data:=ProcParam;
      end;
    end;
     
    //--- example ---
    procedure ThreadTerminate(ProcParam:pointer; Sender:TObject);
    begin
      ... //можно использовать ProcParam, если в него было что-то передано
    end;
     
    MyThread.OnTerminate:=NotifyProcAsEvent(ThreadTerminate, nil); //можно что-то передать через ProcParam

Плюсы: не нужно объявлять класс (и тем самым засорять код лишней VMT ;) ), можно передать любой доп.параметр, можно использовать вложенную\локальную функцию при условии, что компилятор разрешит это сделать (D3 вообще не разрешал, D7 разрешает, если ф-я не обращается к локальным переменным и параметрам вышестоящей функции).

Автор: --Ins-- 14.03.12, 12:07
Цитата leo @
А мой-то чем грязен, только тем, что непонятен "непосвященным"? Грязный этот тот, который либо использует недокументированные возможности, либо может привести к непредсказуемым ошибкам из-за отключения проверки типов, передачи какого-то мусора и т.п. В данном же случае вполне "законно" вместо Self передается nil, что вполне допустимо, если этот параметр в обработчике не используется, а если используется, то получим вполне предсказуемый AccessViоlation


Нет, он грязен тем, что работает только благодаря некоторым особенностям "подкапотного" механизма, и приведет к багу при первом же явном или неявном (вызов виртуального/динамического метода например) обращении к Self. Нельзя вызывать методы для невалидных ссылок, даже несмотря на то, что очень хочется и кажется что можно.

Цитата leo @
о, боже, что на это скажет --Ins--


Инсу интереснее и кажется более оправданным случай, когда наоборот ;)

Добавлено
Цитата leo @
и тем самым засорять код лишней VMT


ОМГ, тоже мне проблема в 21-м веке :wall:

Автор: Fr0sT 14.03.12, 13:20
Метод leo более опасен, т.к. можно ненароком забыться и обратиться к полю объекта. Для классовых же методов компилятор не даст "выстрелить себе в ногу".
Кроме того, обкатал этот метод в своих приложениях - выяснилась ещё одна приятная особенность: если добавить классовых переменных, можно на халяву получить синглтон :)

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

Автор: leo 14.03.12, 17:40
:offtop:
Цитата --Ins-- @
Нет, он грязен тем, что ...

Не надо обобщать, т.к. в данном случае речь идет об особом случае: и способ с классовым методом и способ с nil-указателем НЕ предназначены для обращения к полям объекта и тем более к виртуальным методам. Это просто "затычка" для редких ситуаций, когда обработка производится не в методе класса, а в обычной процедуре и соот-но "некуда пристроить" оработчик. А накосячить из-за собственной забывчивости или тупости можно где-угодно (например, с теми же нетипированными var-параметрами или переполнением буфера), и приводить это может действительно к трудновыявляемым багам, а не к явному AV как в случае с nil-указателем. К тому же против забывчивости есть хорошее средство - не лениться писать комментарии, особенно при наличии каких-то ограничений в использовании.
PS: Разумеется я ни в коей мере не агитирую за использование подобных трюков, но думаю, что в FAQ заметка об их существовании не помешает - разумеется с предупреждением об отказе от ответственности за причиненный ущерб :)

Цитата --Ins-- @
ОМГ, тоже мне проблема в 21-м веке

Проблемы 21 века - это чрезмерное потребление "ресурсов", приводящее к ожирению и мировым кризисам, а также искуственно насаждаемое отупление масс, приучаемых к ЕГЭ и шаблонному мышлению :D

Цитата Fr0sT @
Для классовых же методов компилятор не даст "выстрелить себе в ногу".

Ага, только если мы рассуждаем о забывчивости на грани бездумности, то с таким же успехом можно забыть написать приставку class :D

Автор: --Ins-- 14.03.12, 21:10
Цитата leo @
Это просто "затычка" для редких ситуаций, когда обработка производится не в методе класса, а в обычной процедуре и соот-но "некуда пристроить" оработчик.


Частный, общий, редкий, частый - от этого грязность не зависит :) Ты сам дал выше правильное определение грязности

Цитата leo @
А накосячить из-за собственной забывчивости или тупости можно где-угодно (например, с теми же нетипированными var-параметрами или переполнением буфера), и приводить это может действительно к трудновыявляемым багам, а не к явному AV как в случае с nil-указателем.


Если я накосячу из-за того, что попытаюсь сделать что-то нелегальное сам - вина будет моя. Если же я, выполнив совершенно легальную и безобидную операцию (обращение к Self внутри метода), получу косяк - тут извините прокляну того, кто мне такую свинью подложил - и буду прав.

Цитата leo @
К тому же против забывчивости есть хорошее средство - не лениться писать комментарии, особенно при наличии каких-то ограничений в использовании.


У Фаулера это первое правило обнаружение говнокода - если где-то стоит/хочется написать комментарий, без которого поведение кода не очевидно. И я с этим утверждением категорически согласен :)

Цитата leo @
PS: Разумеется я ни в коей мере не агитирую за использование подобных трюков, но думаю, что в FAQ заметка об их существовании не помешает - разумеется с предупреждением об отказе от ответственности за причиненный ущерб


Я думаю что с примерно такой фразы лучше и начинать. А еще лучше - и заканчивать :D Хотя я раньше сам любил такие всяческие грязненькие штучки и хаки - правда скорее из спортивного интереса, нежели из практического :yes:

Цитата leo @
Проблемы 21 века - это чрезмерное потребление "ресурсов", приводящее к ожирению и мировым кризисам, а также искуственно насаждаемое отупление масс, приучаемых к ЕГЭ и шаблонному мышлению


Интересное замечание, но и с ним можно поспорить... Ну, скажем, пожалуй ожирение лучше чем недоедание, а экономические циклы с периодически случающимися кризисами, сопровождающиеся падением доходов населения с 2500 евро до 2000 евро - лучше чем "министерство изобилия увеличивает недельную норму потребление шоколада до 30 грамм"... Я думаю понятно к чему я клоню ;) Проблемы 21-го века они ступенькой выше проблем 20-го, т.е. подразумевают что они уже решены и теперь можно двигаться дальше. Применительно к сабжу - никакой проблемы в современных условиях наличие лишней VMT не создает. В смысле вообще никакой

PS: ну не такой уж и оффтоп :D

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)