Реализация правильного события C # (не делегата) в Java - PullRequest
0 голосов
/ 12 июня 2019

Как уже упоминалось в этом SO ответе и других постах в том же вопросе, делегаты C # могут быть реализованы с использованием интерфейсов или Java FuncationInterfaces.

Однако я хочу реализовать правильную модель событий, а не модель делегата в Java. Для краткого изложения различий, пожалуйста, смотрите this . Особенно первый комментарий.

Вот что я пробовал до сих пор:

Event.java

public class Event {
    public interface EventHandler{
        void invoke();
    }

    private Set<EventHandler> mEventHandlers = new HashSet<>();

    public void add(EventHandler eventHandler){
        mEventHandlers.add(eventHandler);
    }

    public void remove(EventHandler eventHandler){
        mEventHandlers.remove(eventHandler);
    }

    public void invoke(){
        for(EventHandler eventHandler : mEventHandlers){
            if(eventHandler!=null) {
                eventHandler.invoke();
            }
        }
    }
}

EventPubisher.java

public class EventPublisher {

    public Event ValueUpdatedEvent;

    public void UpdateValue(){
        ValueUpdatedEvent.invoke();
    }
}

EventConsumer.java

public class EventConsumer {
    EventPublisher ep = new EventPublisher();

    public EventConsumer(){
        ep.ValueUpdatedEvent.add(this::ValueUpdatedEventHandler);
    }

    private void ValueUpdatedEventHandler(){
        // do stuff
    }
}

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

public class EventConsumer {
.....
    private void abuse(){
         ep.ValueUpdatedEvent.invoke();
    }
}

И это именно то, что ограничивают события. Событие должно быть инициировано только из декларирующего класса, а не извне.

Ответы [ 2 ]

0 голосов
/ 19 июля 2019

Если вы хотите избежать методов добавления / удаления на вашем издателе, можете ли вы использовать приведенный ниже код?Он использует два класса (Event и EventImpl) для разделения добавления / удаления и вызова, поэтому вызов может быть закрытым для издателя.Он универсален, поэтому можно использовать разные интерфейсы слушателя.

Этот код экономит много дубликатов, но как вы думаете, будет ли он считаться идиоматическим @JonSkeet?

Вот классы событий:

class Event<TListener> {
    private final Set<TListener> listeners;

    Event(Set<TListener> listeners) {
        this.listeners = listeners;
    }

    public void add(TListener listener) {
        listeners.add(listener);
    }

    public void remove(TListener listener) {
        listeners.remove(listener);
    }
}

class EventImpl<TListener> {
    private Set<TListener> listeners = new HashSet<>();
    private Event<TListener> event = new Event<>(listeners);

    Event<TListener> getEvent() {
        return event;
    }

    interface Invoker<TListener> {
        void invoke(TListener listener);
    }

    public void invoke(Invoker<TListener> invoker) {
        for (TListener listener : listeners){
            if (listener != null) {
                invoker.invoke(listener);
            }
        }
    }
}

А вот пример издателя и подписчика с некоторым тестовым кодом для их применения:

class MyEventPublisher {
    interface Listener {
        void listenToThis(int intArg, String stringArg, Object... etc);
    }

    private EventImpl<Listener> eventImpl = new EventImpl<>();

    Event<Listener> getEvent() {
        return eventImpl.getEvent();
    }

    void somethingCausingAnEvent() {
        eventImpl.invoke(
                listener -> listener.listenToThis(1, "blah", 10,11, 12));
    }
}

class MyEventSubscriber {
    private String eventRecord = "";

    MyEventSubscriber(MyEventPublisher publisher) {
        publisher.getEvent().add(
                (intArg, stringArg, etc) -> eventRecord += intArg + stringArg + Arrays.toString(etc));
    }

    String getEventRecord() {
        return eventRecord;
    }
}

public class TestEvents {
    @Test
    public void testEvent() {
        MyEventPublisher p = new MyEventPublisher();
        MyEventSubscriber s = new MyEventSubscriber(p);
        p.somethingCausingAnEvent();
        assertEquals("1blah[10, 11, 12]", s.getEventRecord());
    }
}
0 голосов
/ 12 июня 2019

Как упомянул @Jon Skeet в комментариях, изменение кода, как показано ниже, отвечает моим требованиям:

EventPubisher.java

public class EventPublisher {

    private final Event ValueUpdatedEvent = new Event();

    public void addEventHandler(Event.EventHandler eventHandler){
        ValueUpdatedEvent.add(eventHandler);
    }

    public void removeEventHandler(Event.EventHandler eventHandler){
        ValueUpdatedEvent.remove(eventHandler);
    }

    public void UpdateValue(){
        ValueUpdatedEvent.invoke();
    }
}

EventConsumer.java

public class EventConsumer {
.....
    private void abuse(){
        // ep.ValueUpdatedEvent.invoke(); //Compilation error
    }
}
...