суббота, Февраль 13, 2010

bicycle driven development

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


class Dispatcher {
public void subscribe(Object observer);
public<T> T invoke(Class<T> cl);
}

class Observable {
private Dispatcher dispatcher;
public interface IEventHandler {
void onEvent(Some args);
}
public void subscribe(Object observer) {
dispatcher.subscribe(observer);
}
public void doStuff() {
Some stuff = getStuff();
dispatcher.invoke(IEventHandler.class).onEvent(stuff);
}
}

class Observer implements Observable.IEventHandler {
public void onEvent(Some stuff) {
dealWith(stuff);
}
}


не дотнетный сахар, конечно, но уже почти то, что нужно. впрочем, не обошлось и без проблем.
во-первых неясно, что делать с возвращаемыми значениями. вернуть я могу ровно один объект, но при последовательном вызове всех подписавшихся вернется от нуля до бесконечности. какой из них возвращать - не ясно. когда подписчиков несколько, дотнет возвращает значение последнего вызова. но что делать если ни один из подписчиков нужного интерфейса не реализует? в дотнете это невозможно, там любой ненулевой ивент всегда имеет хотябы одного подписчика, а я пока возвращаю getDefaultValue(). все хорошо, но ломается на value-типах. похоже, что жава отдает на это всегда null, который при попытке кастовать (где-то в глубине рефлекшна, я возвращаю всегда Object) его к инту бросает NPE.
во-вторых рефлекшн имеет пачку странных особенностей. самая мелкая их них - инстанцирование прокси возвращает Object, а не проксю нужного типа. я могу тут же все это скастовать куда надо, но нафига? оно же сразу знает что я создаю. чего бы это и не возвращать? следующая неприятная мелочь уже серьезнее. делать проксю можно только по интерфейсу, однако класса Interface (навроде Class) у жавы нет. т.е. засунуть туда можно любой класс. и огрести рантайм проблем.
в-третьих это рефлекшн со всеми вытекающими тормозами и хреновыми оптимизациями в jit'е. гугл говорит, что рефлекшн не так уж плох в последних версиях, но он определенно хуже прямых вызовов. еще он говорит, что сделано все это не особо хитро - можно подкопаться к класслоадеру и сунуть в него нагенеренный байткод класса, где каждый метод напрямую вызывает всех подписчиков без всякого рефлекшна. тогда все тормоза останутся только при генерации прокси, которая дальше будет работать как обычный класс:

class ProxyClass implements IRequestedInterface {
private Set<Object> handlers;
void onEvent(Some args) {
for (Object handler : handlers)
if (handler instanceof IRequestedInterface)
handler.onEvent(args);
}
}


и на последок у меня пара вопросов к Кэпу: как такое делают нормальные люди? что можно изменить в моей реализации, если нормальные люди вдруг это не делают никак?

Комментарии: 7:

Blogger uthark сказал(а)...

Лёха, вообще в коробке с явой есть вот классы Observer и Observable

А вообще есть либа http://jnotification.sourceforge.net/, посмотри её, может, поможет тебе. :)

8:22 PM  
Blogger swined сказал(а)...

Observer и Observable я смотрел в первую очередь. они нетипизированные, требуют кучи промежуточного кода для использования и выглядят чуть более, чем уебищно.
а либу посмотрю, да.

8:28 PM  
Blogger swined сказал(а)...

чото странная там либа. в сф нету ни кода ни документации :)

8:38 PM  
Blogger uthark сказал(а)...

Ну так можно же обёртку навернуть поверх этих классов.

12:13 AM  
Blogger uthark сказал(а)...

Кстати, вот http://code.google.com/p/monsoon-events/

12:14 AM  
Blogger swined сказал(а)...

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

12:24 AM  
Blogger uthark сказал(а)...

Лёха, ещё есть вот такая либа: http://www.eventbus.org/

9:54 AM  

Отправить комментарий

Подпишитесь на каналы Комментарии к сообщению [Atom]

<< Главная страница