I need to define a generic Bus interface that can either be a CommandBus
or QueryBus
.
public interface Bus {
<T> T execute(BusRequest<T> request);
}
public interface BusRequest<T> {
}
The above example works, but I'll want to replace BusRequest
by another generic that will extend BusRequest
, so that you could do the following:
public interface CommandBus extends Bus<Command> {
// Inherited method would turn into something like
// <T> T execute(Command<T> cmd);
}
public interface Command<T> extends BusRequest<T> {
}
How would I define the Bus
interface to do that? Is it possible in Java?
I have tried:
public interface Bus<R extends BusRequest> {
<T> T execute(R<T> request);
}
However it says:
Type 'R' does not have type parameters
You can't express this type relationship in a way the Java compiler will understand. But you could get a similar effect by making BusRequest
types know about the type of Bus
they're supposed to be used with, and by making Bus
know what type of Bus it is.
For example:
// B is the type of the Bus subclass
public interface Bus<B extends Bus<B>> {
// R is the response type, and Q is the request type
<R, Q extends BusRequest<R, B>> R execute(Q request);
}
// a request is parameterized by R, the response type, and B, the bus type
public interface BusRequest<R, B extends Bus<B>> {}
// A CommandBus is a Bus for CommandBusRequests
public static class CommandBus implements Bus<CommandBus> {
@Override
public <R, Q extends BusRequest<R, CommandBus>> R execute(Q request) {
System.out.println("Got request of type: " + request.getClass());
return null;
}
}
public interface CommandBusRequest<T> extends BusRequest<T, CommandBus> {}
// StringCommandBusRequest is a BusRequest for a CommandBus that requests
// a response of type String
public static class StringCommandBusRequest
implements CommandBusRequest<String> {}
These types all compile and type check, and the result looks like what I think you want:
public static void main(String[] args) throws Exception {
StringCommandBusRequest request = new StringCommandBusRequest();
Bus<CommandBus> bus = new CommandBus();
String result = bus.execute(request);
}
Demo: https://ideone.com/U1TLXr
To be more useful, however, you'll probably need some bus-specific payload information in each request object. As this stands, each request type is tied to a bus type, but the bus isn't able to extract bus-specific data from the request object, because for example there could be more than one class that implements BusRequest<?, CommandBus>
.
To solve this, we just need to introduce another(!) type parameter to keep track of the payload type. For example:
public interface Bus<P, B extends Bus<P, B>> {
<R, Q extends BusRequest<R, P, B>> R execute(Q request);
}
public interface BusRequest<R, P, B extends Bus<P, B>> {
P getPayload();
}
public static class CommandBus
implements Bus<CommandBusRequestPayload, CommandBus> {
@Override
public <R, Q extends BusRequest<R, CommandBusRequestPayload, CommandBus>>
R execute(Q request) {
CommandBusRequestPayload payload = request.getPayload();
System.out.println("Got payload: " + payload);
return null;
}
}
public static abstract class CommandBusRequest<T>
implements BusRequest<T, CommandBusRequestPayload, CommandBus> {
@Override
public CommandBusRequestPayload getPayload() {
return new CommandBusRequestPayload();
}
}
public static class CommandBusRequestPayload {}
public static class StringCommandBusRequest
extends CommandBusRequest<String> {}
Demo with payload: https://ideone.com/2aUwMW
A Kind?
No. You'll need Scala (or Haskell) for that.