LINUX.ORG.RU

Декораторы в Java для listener'ов

 


0

1

Надоело каждый раз вызывать листенеры в цикле, придумал такую вещь:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

public class ListenerProxyHolder<T> implements InvocationHandler {
    private final T proxyObject;
    private final List<T> listeners = new ArrayList<T>();

    public ListenerProxyHolder(Class<T> interfaceClass) {
        proxyObject = (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, this);
    }

    public void addListener(T t) {
        listeners.add(t);
    }

    public void removeListener(T t) {
        listeners.remove(t);
    }

    public T getProxy() {
        return proxyObject;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        for (T listener : listeners) {
            method.invoke(listener, objects);
        }
        return null;
    }
}

Используется так:

public class Main {
    private ListenerProxyHolder<Listener> proxyHolder = new ListenerProxyHolder<Listener>(Listener.class);
    public Main() {
    }

    public void addListener(Listener listener) {
        proxyHolder.addListener(listener);
    }

    public void removeListener(Listener listener) {
        proxyHolder.removeListener(listener);
    }

    public void sum(int... args) {
        int sum = 0;
        for (int a : args) {
            sum += a;
            proxyHolder.getProxy().onResultReady(sum);
        }
    }

    public static void main(String[] args) {
	    Main main = new Main();

        main.addListener(new Listener() {
            @Override
            public void onResultReady(int result) {
                System.out.println("Listener 1: " + result);
            }
        });

        main.addListener(new Listener() {
            @Override
            public void onResultReady(int result) {
                System.out.println("Listener 2: " + result);
            }
        });

        main.sum(1, 2, 3);
    }

    private static interface Listener {
        void onResultReady(int result);
    }
}

Таким образом код прохода по всем листенерам находится в одном месте, а не раскидан по местам вызова. Причем вместо цикла, может быть и более сложный код, вроде отправки Runnable'а в EventLoop.

Так вообще принято делать, или java-way — это писать кучу однотипного кода руками?

Если надоела java, может, лучше использовать Scala или Jython? По сравнению с питон декораторами - это не так уж и удобно, через прокси кидать код.

menangen ★★★★★ ()

так делать можно, только дизайн - говно

subwoofer ★★★★★ ()
Последнее исправление: subwoofer (всего исправлений: 1)

Так вообще принято делать, или java-way — это писать кучу однотипного кода руками?

О боже, они... они начинают о чем-то догадываться!

Сколько джавистов нужно, чтобы вкрутить лампочку^W^Wпонять, что паттерны наглядный пример отсутствия code reuse?

Kuzy ★★ ()

перетащил свой цикл отсылки сообщений в отдельный класс? А в чем новизна?

Сим разрешаю тебе любой повторяющийся цикл вытаскивать в отдельный метод и отдельный класс.

stevejobs ★★★☆☆ ()
Ответ на: комментарий от menangen

По сравнению с питон декораторами

щито? питонячий декоратор - это просто обертка над функцией. У ТСа лиснеры и так функции, т.е. делают то же самое. Если нужен сахарок, есть отличный Spring AOP, или если вручную - бин-постпроцессоры, в которых можно вручную навесить прозрачный прокси. Чото ты не шаришь.

stevejobs ★★★☆☆ ()
Ответ на: комментарий от stevejobs

свой цикл

не цикл, а циклы. в интерфейсе может быть много методов и вызываться они могут из многих мест.

crowbar ()

Плохой вариант. Рефлексию на ровном месте развёл. Но в принципе жить можно, если листенеров много и много разных методов с разным числом аргументов. Другого варианта наверное не придумать.

Legioner ★★★★★ ()
Последнее исправление: Legioner (всего исправлений: 1)

все это похоже на выполнение foreach по списку listener'ов, делается в одну строчку. Вам java 8 еще не завезли?

maxcom ★★★★★ ()
Ответ на: комментарий от Legioner

Но в принципе жить можно, если листенеров много и много разных методов с разным числом аргументов

В том и дело, все ради того чтобы избавится от однотипного кода вида

private void notifySomeEvent() {
    handler.post(new Runnable() {
        @Override
        public void run() {
            for (Listener listener : listeners) {
                listener.someEvent();
            }
        }
    });
}
crowbar ()
Ответ на: комментарий от subwoofer

только дизайн - говно

А как лучше? Есть еще вариант сделать такое требование, чтобы нужный листенер наследовался от базового интерфейса

interface ProxyBase<T> {
    void registerListener(T listener);
    void unregisterListener(T listener);
}

И затем в InvocationHandler проверять что вызваны эти методы и соответственно обновлять ArrayList. Тогда получение объекта-обертки будет происходить через статический метод.

crowbar ()
Ответ на: комментарий от crowbar

А как лучше?

ну во первах наружу не должен торчать тот факт что это instanceof InvocationHandler

во вторых его стоит получать через фабрику которой ты передаешь интерфейс(ы) нужного листенера

в третьих использовать фабрику которая возвращает

<I, T extends I & ProxyBase> create(Class<I> listenerIface)

см http://docs.oracle.com/javase/tutorial/java/generics/bounded.html

в четвертых если у тебя все интерфейсы листенеров в руках то это делается без проксей просто сведением всех интерфейсов к виду функции

subwoofer ★★★★★ ()
Ответ на: комментарий от stevejobs

питонячий декоратор - это просто обертка над функцией

4.2 же лютое

shty ★★★★★ ()
Ответ на: комментарий от subwoofer

пошли дурака программировать он и на декораторы станет молиться

shty ★★★★★ ()
Ответ на: комментарий от stevejobs

listeners.stream()

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

subwoofer ★★★★★ ()
Ответ на: комментарий от stevejobs

ты в сорцы явы аглядываешь только по утрам 1го января?

там создается splititerator + head, т.е. один этот метод толще стандартного foreach в два раза, не говоря уже о том что рождается еще в той строке

subwoofer ★★★★★ ()
Ответ на: комментарий от subwoofer

тогда использование Scala, Groovy, JRuby и особенно Clojure вообще можно считать с самоубийством? xD

stevejobs ★★★☆☆ ()
Ответ на: комментарий от stevejobs

толстота - это вечный спутник жависта

нет мой дорогой, насиловать gc во всех позах на ровном месте, при том что ты не знаешь что там в event происходит - вот что самоубийство

если там просто батон и крыжик - то и фиг с ним, если это например жирная GIS с мульеном объектов на экране которая позволяет inplace редактирование сложных объектв - то там ты уже огребешь с таким подходом

subwoofer ★★★★★ ()
Ответ на: комментарий от subwoofer

вот тут измеряют скорость: http://stackoverflow.com/questions/22658322/java-8-performance-of-streams-vs-...

насчитали совсем незначительную разницу

давай я с утра загляну в сплитератор, сейчас очень спать охота.

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

с мульеном объектов на экране которая позволяет inplace редактирование сложных объектв - то там ты уже огребешь с таким подходом

что это за гуманитарщина? Что изменилось кокнкретно, в терминах языка, скажи? Количество объектов увеличилось? На напиши там не stream, а parallelStream, если так угодно...

stevejobs ★★★☆☆ ()
Последнее исправление: stevejobs (всего исправлений: 1)
Ответ на: комментарий от subwoofer

если уж на то пошло, то прокси, которые городит Spring AOP тоже могут притащить тормоза вплоть до на порядок

но т.к. обычно вся эта шняга не является бутылочным горлышком, на нее можно смело забить

stevejobs ★★★☆☆ ()
Ответ на: комментарий от stevejobs

насчитали совсем незначительную разницу

они там насчитали три разных результата в одном из которых потоки были быстрее, что лишний раз доказывает.... идиотизм, но не будем о грустном 8)

почему-то, чисто интуитивно, мне кажется, что замедления больше порядка там не будет, а с порядком вполне можно жить, если это сделает код чистым и шелковистым

facepalm¹⁰⁰ какой в попу чистый код если приложение тормозит при перетаскивании точки на карте?

что это за гуманитарщина

ага, когда теоретик видит архитектуру графического редактора он говорит «гуманитарщина»

subwoofer ★★★★★ ()
Ответ на: комментарий от stevejobs

прокси, которые городит Spring AOP тоже могут притащить тормоза вплоть до на порядок

на несколько порядков, другое дело что там где нужна производительность - порнографию не используют

тотже спринг тебе позволяет сделать прокси рефлекшеном или байткодом, или вообще не делать

subwoofer ★★★★★ ()
Ответ на: комментарий от subwoofer

если приложение тормозит при перетаскивании точки на карте?

а оно и не тормозит. Или ты взял слишком слабый сервер.

архитектуру графического редактора

что за гуманитарщина блин, ты так и не сказал чему там тормозить

графического редактора

графический редактор пусть пользователь рендерит своим процом i7 в своём браузере, нечего на сервер всякую гадость тащить

stevejobs ★★★☆☆ ()
Ответ на: комментарий от subwoofer

другое дело что там где нужна производительность - порнографию не используют

да, критические участки переписывают на C и OCaml. Ну ладно, значит надо знать ажно 2-3 языка, а не одну джаву во все места пихать.

stevejobs ★★★☆☆ ()
Ответ на: комментарий от stevejobs

ты взял слишком слабый сервер.

пользователь рендерит своим процом i7 в своём браузере,

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

subwoofer ★★★★★ ()
Ответ на: комментарий от stevejobs

да, критические участки переписывают на C и OCaml. Ну ладно, значит надо знать ажно 2-3 языка, а не одну джаву во все места пихать.

т.е. все на jruby, а критические на c? это называет «критинические» и всю команду девелоперов надо посыпать дустом чтобы зараза не распространялась

subwoofer ★★★★★ ()
Ответ на: комментарий от subwoofer

во-первых, зачем на джабе писать десктопные приложения? На шиндовсе есть нативный C#/.NET, на OSX есть нативный objc/swift/cocoa, линукс для десктопа не очень-то нужен ибо линукс - это сервер (а если сильно нужен, то есть нативно выглядящее Qt).

во-вторых, ты видел хоть одно тормозящее приложение? Моему компу уже лет пять от роду, не помню ничего, чего бы на нем тормозило, включая Eclipse и Idea. Вот игрушка Arma3 тормозит на максималках, но это совершенно отдельная песня...

stevejobs ★★★☆☆ ()
Ответ на: комментарий от stevejobs

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

в этом больше смысла чем писать а лоре

во-вторых, ты видел хоть одно тормозящее приложение?

да

subwoofer ★★★★★ ()
Ответ на: комментарий от subwoofer

да

какое это приложение? Запустил сейчас IntelliJ 14 с проектом-хэлловорлдом, оно грузилось всего 11 секунд c загрузкой около 75% от процессора i7 2600K (купленного в 2011 году). После загрузки оно весит 435 мегабайтов рамы, и в состоянии бездействия жрет примерно 0% процессора. Даже не знаю, какое десктопное приложение еще толще запустить, чтобы оно начало тормозить. Фотошоп? Автокад? Три автокада?

мне кажется, что решается надуманная проблема. Надо это предположение проверить.

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

stevejobs ★★★☆☆ ()
Последнее исправление: stevejobs (всего исправлений: 1)
Ответ на: комментарий от stevejobs

графического редактора

Запустил сейчас IntelliJ 14 с проектом-хэлловорлдом

это был определенно не тот доктор

Даже не знаю, какое десктопное приложение еще толще запустить

поробуй в idea открыть не хелловорлд

первый же неправильно написанный алгоритм сожрет гораздо больше ресурсов

эта херня и есть неправильно написанный алгоримт, и он жрет все ресусры как только вызывается в java.awt.EventQueue

ps. вот эта лажа http://i.stack.imgur.com/iBVaM.png - если в ней открыть наш проект написанный на netbeans platform - просто залипала на многоминут и если она таки отрисуется то тык мышкой опять приводил к залипанию, писали такиеже как и ты

subwoofer ★★★★★ ()
Последнее исправление: subwoofer (всего исправлений: 1)
Ответ на: комментарий от stevejobs

Запустил сейчас IntelliJ 14 с проектом-хэлловорлдом, оно грузилось всего 11 секунд c загрузкой около 75% от процессора i7 2600K (купленного в 2011 году). После загрузки оно весит 435 мегабайтов рамы

http://img0.joyreactor.cc/pics/post/java-песочница-921057.jpeg

Solace ★★ ()
Ответ на: комментарий от stevejobs

Да это херня, сплитератор - копейки, синки с прочим барахлом и то больше займут, но это все подкапотно. А поскольку в ОП написана какая-то симуляция блямб череж жёпу, то ну его.

anonymous ()

Это называется «смотрите как прикольно я умею удалять гланды через задний проход».

ya-betmen ★★★★★ ()
Ответ на: комментарий от crowbar

В том и дело, все ради того чтобы избавится от однотипного кода вида

По возможности лучше избавиться от рефлексии.Можно писать код вида

    listeners.forEach(l -> l.someEvent(arg1, arg2));

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

Кстати перед перебором листенеров их очень желательно скопировать в отдельный массив и перебирать его. Потому что возникает желание в листенере убрать себя из списка, а это ConcurrentModificationException. Ну или хранить листенеры в структуре, которая нормально относится к модификации при переборе.

Legioner ★★★★★ ()
Последнее исправление: Legioner (всего исправлений: 3)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.