0

As already mentioned in this SO answer and the other posts in the same question, C# delegates can be implemented using interfaces or Java FuncationInterfaces.

However I am looking to implement a proper event model and not a delegate model in Java. For a brief on the difference of the two, please see this. Especially the first comment.

Below is what I have tried so far:

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
    }
}

The problem with this design is that I can write code like below as well:

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

And this is particularly what events restrict. The event should be raised only from the declaring class and not from outside.

Sisir
  • 4,584
  • 4
  • 26
  • 37
  • 3
    How is your current attempt not working? Can you make your question more specific and thus easier to answer? – Hovercraft Full Of Eels Jun 12 '19 at 17:32
  • I want the invoke method to be called from only EventPublisher and not from any where else. The add() and remove() methods should be accessiable from everywhere – Sisir Jun 12 '19 at 17:46
  • Why have you made `EventPublisher.ValueUpdatedEvent` public? Instead, that should be private, and there should be specific add/remove methods on `EventPublisher` which *use* the `Event` class as an implementation detail. That allows the same class to publish multiple events, e.g. `addClickHandler`, `addQuitHandler` etc. – Jon Skeet Jun 12 '19 at 18:35
  • @JonSkeet That way, basically I will have to write the entire `Event` class code inside the `EventPublisher` only . Now if I want to declare 5 events in the class, the class will become unreadable and worse if I have to declare the same event in a different class, the temple will just have to be duplicated – Sisir Jun 12 '19 at 18:55
  • The reason I have kept the event specific code in a separate class is to re-use. – Sisir Jun 12 '19 at 18:56
  • 1
    @Sisir: No, you don't have to write "the entire Event class code" - you write two methods (add/remove) which delegate to the Event object backing that event. Each method is a single line long, even if Event gets more complex. You wanted C# events - that's what C# events are... pairs of add/remove methods that the compiler knows about. It sounds like you have requirements which you haven't specified. I've explained how you can make this as close to C# events as possible - if you choose to reject that, that's your call, but in that case you shouldn't request a design which *is* C# events. – Jon Skeet Jun 12 '19 at 18:58
  • @JonSkeet This actually helps. This last comment seems like a viable solution. What I was looking for was something similar to C# 5.0 styled auto-implemented events. But I think that will need compiler support. – Sisir Jun 12 '19 at 19:03
  • 1
    @Sisir: Field-like events have been in C# since 1.0, but they're still just pairs of methods, and you're not going to get language support for generating them from vanilla Java. This is why I said before that it would be better to stick to what's idiomatic with the language and platform you're using. – Jon Skeet Jun 12 '19 at 19:09

2 Answers2

1

If you wanted to avoid the add/remove methods on your publisher, could you use the code below? It uses two classes (Event and EventImpl) to separate the add/remove and the invoke, so invoke can be made private to the publisher. It is generic so different listener interfaces can be used.

This code saves a lot of duplicated boilerplate, but do you think it would it be considered idiomatic @JonSkeet?

Here are the event classes:

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);
            }
        }
    }
}

And here is an example of a publisher and subscriber with some test code to exercise them:

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());
    }
}
dominic
  • 401
  • 5
  • 12
0

As mentioned by @Jon Skeet in the comments, changing the code as below meets my requirement:

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
    }
}
Sisir
  • 4,584
  • 4
  • 26
  • 37