How to create a safe type API in Java?

I have a Java client who wants to communicate with the device through messages through serial communication. The client should be able to use a clean API, abstracting the ugly details of serial communication. A client can send many types of messages through this API and receive responses. I am looking for advice that is best suited for implementing this API.

For simplicity, let's say we only have two types of messages: HelloMessage , which launches HelloResponse and InitMessage , which launches InitResponse (in fact, there are many more)

Designing an API (i.e. a Java device abstraction), I could:

One method for message type:

 public class DeviceAPI { public HelloResponse sendHello(HelloMessage){...} public InitResponse sendInit(InitMessage){...} ... and many more message types .... 

This is a safe type. (It can be the same send() method overloaded many times, but it is about the same). But it is very explicit and not very flexible - we cannot add messages without changing the API.

I can also have one sending method that accepts all types of messages:

 class HelloMessage implements Message class HelloResponse implements Response ... public class DeviceAPI { public Response send(Message msg){ if(msg instanceof HelloMessage){ // do the sending, get the response return theHelloResponse } else if(msg instanceof ... 

This simplifies the API (only one method) and allows you to add additional message types later without changing the API. At the same time, this requires the client to check the type of response and apply it to the desired type.

Client Code:

 DeviceAPI api = new DeviceAPI(); HelloMessage msg = new HelloMessage(); Response rsp = api.send(msg); if(rsp instanceOf HelloResponse){ HelloResponse hrsp = (HelloResponse)rsp; ... do stuff ... 

This is ugly in my opinion.

What do you recommend? Are there other approaches that give cleaner results?

Links are welcome! How did others solve this?

+6
source share
4 answers

Here's how to do it in a safe type (and extensible) way using generics:

 public interface MessageType { public static final class HELLO implements MessageType {}; } public interface Message<T extends MessageType> { Class<T> getTypeClass(); } public interface Response<T extends MessageType> { } 

 public class HelloMessage implements Message<MessageType.HELLO> { private final String name; public HelloMessage(final String name) { this.name = name; } @Override public Class<MessageType.HELLO> getTypeClass() { return MessageType.HELLO.class; } public String getName() { return name; } } public class HelloResponse implements Response<MessageType.HELLO> { private final String name; public HelloResponse(final String name) { this.name = name; } public String getGreeting() { return "hello " + name; } } 

 public interface MessageHandler<T extends MessageType, M extends Message<T>, R extends Response<T>> { R handle(M message); } public class HelloMessageHandler implements MessageHandler<MessageType.HELLO, HelloMessage, HelloResponse> { @Override public HelloResponse handle(final HelloMessage message) { return new HelloResponse(message.getName()); } } 

 import java.util.HashMap; import java.util.Map; public class Device { @SuppressWarnings("rawtypes") private final Map<Class<? extends MessageType>, MessageHandler> handlers = new HashMap<Class<? extends MessageType>, MessageHandler>(); public <T extends MessageType, M extends Message<T>, R extends Response<T>> void registerHandler( final Class<T> messageTypeCls, final MessageHandler<T, M, R> handler) { handlers.put(messageTypeCls, handler); } @SuppressWarnings("unchecked") private <T extends MessageType, M extends Message<T>, R extends Response<T>> MessageHandler<T, M, R> getHandler(final Class<T> messageTypeCls) { return handlers.get(messageTypeCls); } public <T extends MessageType, M extends Message<T>, R extends Response<T>> R send(final M message) { MessageHandler<T, M, R> handler = getHandler(message.getTypeClass()); R resposnse = handler.handle(message); return resposnse; } } 

 public class Main { public static void main(final String[] args) { Device device = new Device(); HelloMessageHandler helloMessageHandler = new HelloMessageHandler(); device.registerHandler(MessageType.HELLO.class, helloMessageHandler); HelloMessage helloMessage = new HelloMessage("abhinav"); HelloResponse helloResponse = device.send(helloMessage); System.out.println(helloResponse.getGreeting()); } } 

To add support for the new message type, implement the MessageType interface to create a new message type, implement the Message , Response and MessageHandler interfaces for the new MessageType class and register a handler for the new message type by calling Device.registerHandler .

+1
source

Now I have a fully working example of what you want:

To determine message types:

 public interface MessageType { public static class INIT implements MessageType { } public static class HELLO implements MessageType { } } 

The base classes Message and Response :

 public class Message<T extends MessageType> { } 

 public class Response<T extends MessageType> { } 

Creating custom message and response replies:

 public class InitMessage extends Message<MessageType.INIT> { public InitMessage() { super(); } public String getInit() { return "init"; } } 

 public class InitResponse extends Response<MessageType.INIT> { public InitResponse() { super(); } public String getInit() { return "init"; } } 

Create custom welcome messages and answers:

 public class HelloMessage extends Message<MessageType.HELLO> { public HelloMessage() { super(); } public String getHello() { return "hello"; } } 

 public class HelloResponse extends Response<MessageType.HELLO> { public HelloResponse() { super(); } public String getHello() { return "hello"; } } 

DeviceAPI :

 public class DeviceAPI { public <T extends MessageType, R extends Response<T>, M extends Message<T>> R send(M message) { if (message instanceof InitMessage) { InitMessage initMessage = (InitMessage)message; System.out.println("api: " + initMessage.getInit()); return (R)(new InitResponse()); } else if (message instanceof HelloMessage) { HelloMessage helloMessage = (HelloMessage)message; System.out.println("api: " + helloMessage.getHello()); return (R)(new HelloResponse()); } else { throw new IllegalArgumentException(); } } } 

Note that this requires instanceof -tree, but you need this to handle what this message is.

And a working example:

 public static void main(String[] args) { DeviceAPI api = new DeviceAPI(); InitMessage initMsg = new InitMessage(); InitResponse initResponse = api.send(initMsg); System.out.println("client: " + initResponse.getInit()); HelloMessage helloMsg = new HelloMessage(); HelloResponse helloResponse = api.send(helloMsg); System.out.println("client: " + helloResponse.getHello()); } 

Output:

 api: init client: init api: hello client: hello 

UPDATE Added an example of how to get input from messages that the client wants to send.

+2
source

You may have a message handler system, and your DeviceAPI may choose which handler is appropriate for the incoming message; and pass it to the appropriate message handler:

 class DeviceAPI { private List<Handler> msgHandlers = new ArrayList<Handler>(); public DeviceAPI(){ msgHandlers.add(new HelloHandler()); //Your other message handlers can be added } public Response send(Message msg) throws Exception{ for (Handler handler : msgHandlers) { if (handler.canHandle(msg)){ return handler.handle(msg); } } throw new Exception("No message handler defined for " + msg); } } 

HelloHandler will look like this:

  interface Handler<T extends Message, U extends Response> { boolean canHandle(Message message); U handle(T message); } class HelloHandler implements Handler<HelloMessage, HelloResponse> { @Override public boolean canHandle(Message message) { return message instanceof HelloMessage; } @Override public HelloResponse handle(HelloMessage message) { //Process your message return null; } } 

The same goes for other posts. I'm sure you could make it more elegant, but the idea still remains the same - donot has one monster method with ifs; use polymorphism instead.

+1
source

I don't think this is generally ugly:

 if(rsp instanceOf HelloResponse){ HelloResponse hrsp = (HelloResponse)rsp; ... else if ... 

if you do not have 100 different answers. You can encapsulate many answers in one, depending on the data that they have. For instance:

 class GenericResponse{ private String message; private int responseType; ... } 

I have developed several multiplayer games, and this is a good way to do this. If you have too many different types of messages, you can use generic java types, for example the skiwi example above.

hope this helps

0
source

All Articles