如何在 Java 中设计类型安全消息 API



我有一个Java客户端,它想通过串行通信的消息与设备进行通信。客户端应该能够使用干净的API,抽象出串行通信的丑陋细节。客户端可以通过该 API 发送多种类型的消息并获得响应。我正在寻找哪种方式最适合实现此 API 的建议。

为简单起见,假设我们只有两种消息类型:触发HelloResponseHelloMessage和触发InitResponseInitMessage(实际上,还有更多(

设计 API(即设备的 Java 抽象(我可以有:

每种消息类型一种方法:

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

这是很好的类型安全。(它也可能是很多次相同的send()方法,重载,但这大致相同(。但它非常明确,而且不是很灵活 - 我们无法在不修改 API 的情况下添加消息。

我也可以有一个发送方法,它接受所有消息类型:

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

这简化了 API(仅一种方法(,并允许稍后在不更改 API 的情况下添加其他消息类型。同时,它要求客户端检查响应类型并将其转换为正确的类型。

客户端代码:

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

在我看来,这很丑陋。

你有什么建议?有没有其他方法可以提供更清洁的结果?

欢迎参考!其他人是如何解决这个问题的?

这是一种使用泛型以类型安全(和可扩展(的方式执行此操作的方法:

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

要添加对新消息类型的支持,请实现MessageType接口以创建新的消息类型,为新的MessageType类实现MessageResponseMessageHandler接口,并通过调用Device.registerHandler注册新消息类型的处理程序。

我现在有一个完整的工作示例,说明您想要什么:

要定义消息类型:

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

MessageResponse类:

public class Message<T extends MessageType> {
}

public class Response<T extends MessageType> {
}

创建自定义初始化消息和响应:

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

创建自定义问候消息和响应:

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

请注意,它确实需要一个instanceof树,但您需要它来处理它是什么样的消息。

还有一个工作示例:

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

输出:

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

更新:添加了有关如何从客户端要发送的消息中获取输入的示例。

你可以有一个消息处理程序系统,你的 DeviceAPI 可以选择适合传入消息的处理程序;并将其委托给相应的消息处理程序:

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 看起来像:

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

您的其他消息也是如此。我相信你可以让它更优雅,但想法仍然保持不变 - 不要有一个带有 ifs 的怪物方法;而是使用多态性。

我认为这一点也不丑陋:

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

只要你没有100个不同的回答。您可以将多种响应封装在一个响应中,具体取决于它们拥有的数据。例如:

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

我开发了一些多人游戏,这是一个很好的方法。如果你有太多不同类型的消息,你可以使用通用的java类型,就像上面的skiwi的例子一样。

希望对您有所帮助

最新更新