Factory For A Generic Interface

Viewed 113

I have an interface:

public interface Handler<E extends Event> {
    void handle(E event);
}

And two implementations of it:

public class SignupEventHandler implements Handler<SignupEvent> {
    @Override
    public void handle(SignupEvent event) {}
}

and

public class SignoutEventHandler implements Handler<SignoutEvent> {
    @Override
    public void handle(SignoutEvent event) {}
}

Note that Event in itself is an interface, that is implemented by both SignupEvent and SignoutEvent.

I am wondering what's the correct way to implement a factory for Handler.

public class HandlerFactory {
    public static Handler<? extends Event> getHandler(Event event) {
        if(event.type().equals("SignupEvent")) {
            return new SignupEventHandler();
        } else if(event.type().equals("SignoutEvent")) {
            return new SignoutEventHandler();
        } else {
            throw new IllegalArguementException("Unrecognised event type");
        }
    }
}

The problem I am facing is that I can't do HandlerFactory.getHandler(event).handle(event) as it wants a capture of ? of Event.

Event event = getDeserialisedEvent(message, Event.class);
Handler<? extends Event> handler = HandlerFactory.getHandler(event);
handler.handle(event);    //can't do this

Is there a clean way to accomplish this? One way I found was:

public class HandlerFactory {
    public static <E extends Event> Handler<E> getHandler(E event) {
        if(event.type().equals("SignupEvent")) {
            return (Handler<E>) new SignupEventHandler();    //unchecked cast, but it's guaranteed to be type-safe
        } else if(event.type().equals("SignoutEvent")) {
            return (Handler<E>) new SignoutEventHandler();   //unchecked cast, but it's guaranteed to be type-safe
        } else {
            throw new IllegalArguementException("Unrecognised event type");
        }
    }
}

This approach works, but I wonder if there is a cleaner way to do this.

4 Answers

There are slightly different ways, but they are all basically same. All of them use cast in one way or another.

For example you can use raw types:

public static Handler getHandler(Event  event) 

You may solve it nicely if instead of a factory you have a class that delegates to the right handler.

public class EventHandlerFacade {

    public void handle(Event event) {
        if (event instanceof SignupEvent) {
            new SignupEventHandler().handle((SignupEvent) event);
        }
        else if (event instanceof SignoutEvent) {
            new SignoutEventHandler().handle((SignoutEvent) event);
        }
        else {
            System.out.println("Unhandled event of class " + event.getClass());
        }
    }

}

In this code Java can see that the casts are safe. It will work no matter if the facade creates the handler each time it needs one (as in the code), or it keeps handlers around and reuses them.

You may also want to live with what you have. Generics are for compile-time type checking. When you don’t know the actual (runtime) type of event at compile time, compile-time type checking doesn’t make that much sense. So you may opt to dispense with it.

A possible solution would be replacing handle(E); in Handler with handle(Event);, and then casting Event to wanted sub-class in classes inherited from Handler. I do not think there is a way to do exactly what you want because the compiler does not know the generic type of Handler.

We could try something like this.
Problem is void handle(E event); is not able to capture type argument. So we make it capture by declaring it like <T extends Event> void handle(T event). Rest of the explanation is after following code.

public class Test{
    
    public static void main(final String[] args) throws Exception {
        Event e =new SignupEvent();
        Handler<? extends Event> handler = HandlerFactory.getHandler(e);
        handler.handle(e); //yeppie!

        e = new SignoutEvent();
        handler = HandlerFactory.getHandler(e);
        handler.handle(e);
  }
}

class HandlerFactory {
    public static Handler<? extends Event> getHandler(Event event) {
        if(event instanceof SignupEvent)
            return new SignupEventHandler();
        return new SignoutEventHandler();
    }
}

abstract class Handler<E extends Event> {
    @SuppressWarnings("unchecked") //<-- we know what we are doing
    final <T extends Event>  void handle(T event) {
        handlerHelper((E)event);
    }
    
    protected abstract void handlerHelper(E event);
}

class SignupEventHandler extends Handler<SignupEvent> {
    @Override
    protected void handlerHelper(SignupEvent event) {
        System.out.println("Handling sign UP");
    }
}

class SignoutEventHandler extends Handler<SignoutEvent> {
    @Override
    protected void handlerHelper(SignoutEvent event) {
        System.out.println("Handling sign OUT");
    }
}

interface Event{
    
}
class SignupEvent implements Event{
    
}
class SignoutEvent implements Event{
    
}

Output:

Handling sign UP
Handling sign OUT

Now handle method is able to capture the type with the method's type parameter. But we want to enforce the type parameter of the Handler class. So we create separate handlerHelper method which uses class's type parameters. And call it from our handle method. Now we get uncheked cast warning, which we'll suppress.
We'll make handle method final. So that subclasses will not override it. As interfaces can't have final methods, we'll have to make Handler class abstract.
Also, we make handlerHelper method protected so that API users will call only the handle method.

Related