如何在Java中设计类型安全的消息API?
我有一个Java客户端,它希望通过串行通信通过消息与设备通信。客户端应该能够使用干净的API,抽象出串行通信的丑陋细节。客户端可以通过该API发送多种类型的消息并获得响应。我正在寻找实现这个API的最佳方法 为简单起见,假设我们只有两种消息类型:如何在Java中设计类型安全的消息API?,java,api,Java,Api,我有一个Java客户端,它希望通过串行通信通过消息与设备通信。客户端应该能够使用干净的API,抽象出串行通信的丑陋细节。客户端可以通过该API发送多种类型的消息并获得响应。我正在寻找实现这个API的最佳方法 为简单起见,假设我们只有两种消息类型:hellomemessage触发helloreresponse和InitMessage触发InitResponse(实际上,还有更多) 设计API(即设备的Java抽象)我可以: 每个消息类型一个方法: public class DeviceAPI {
hellomemessage
触发helloreresponse
和InitMessage
触发InitResponse
(实际上,还有更多)
设计API(即设备的Java抽象)我可以:
每个消息类型一个方法:
public class DeviceAPI {
public HelloResponse sendHello(HelloMessage){...}
public InitResponse sendInit(InitMessage){...}
... and many more message types ....
这是很好的类型安全。(它也可能多次使用相同的send()
method,重载,但大致相同)。但是它非常明确,也不是很灵活——我们不能在不修改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 class INIT implements MessageType { }
public static class HELLO implements MessageType { }
}
基本消息
和响应
类:
public class Message<T extends MessageType> {
}
请注意,它确实需要一个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
更新:添加了如何从客户端想要发送的消息中获取输入的示例。我认为这一点都不难看:
if(rsp instanceOf HelloResponse){
HelloResponse hrsp = (HelloResponse)rsp;
...
else if ...
只要你没有100个不同的回答。您可以将多种响应封装在一个响应中,具体取决于它们所拥有的数据。例如:
class GenericResponse{
private String message;
private int responseType;
...
}
我开发了一些多人游戏,这是一个很好的方法。
如果有太多不同类型的消息,可以使用通用java类型,如上面的skiwi示例
希望它有帮助您可以拥有一个消息处理程序系统,并且您的设备API可以选择适合传入消息的处理程序;并将其委托给相应的消息处理程序:
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);
}
}
类设备API{
私有列表msgHandlers=newArrayList();
公共设备API(){
添加(新的HelloHandler());
//可以添加其他消息处理程序
}
公共响应发送(消息消息消息)引发异常{
for(处理程序:MsgHandler){
if(handler.canHandle(msg)){
返回handler.handle(msg);
}
}
抛出新异常(“没有为“+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;
}
}
接口处理程序{
布尔canHandle(消息);
U句柄(T消息);
}
类HelloHandler实现处理程序{
@凌驾
公共布尔canHandle(消息){
返回HelloMessage的消息实例;
}
@凌驾
公共Hello响应句柄(HelloMessage消息){
//处理你的信息
返回null;
}
}
其他消息也一样。我相信你可以使它更优雅,但想法仍然是一样的-没有一个怪物的方法与ifs;请改用多态性。以下是一种使用泛型以类型安全(且可扩展)的方式实现多态性的方法:
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> {
}
要添加对新消息类型的支持,请实现
MessageType
接口要创建新消息类型,请实现message
,Response
和MessageHandler
为新的MessageType
类接口,并通过调用Device为新的消息类型注册处理程序。registerHandler
是否可以使用带有枚举的参数化类型?或者这根本不可能。你检查“响应”的类型,然后抛出HelloMessage。这似乎很奇怪。无论如何,您可以在实现类中隐藏“instanceOf”和“casting”操作。我认为检查类型一点也不难看,这可能也是我实现它的方式。如果你不想这样,我认为重新考虑的正确地点是你的模型;是什么使这些响应成为不同的对象?例如,它们不能是泛型吗?@Marktieleman这些信息有很多相似之处,但也有很多不同之处。假设Hello响应包含设备的序列号。我希望HelloResponse对象具有getSerialNumber()方法。我怎样才能用泛型实现这一点?@arjacsoh抱歉,这是一个打字错误。谢谢你发现了。谢谢,这很有趣。假设响应包含不同的信息,我如何访问它?@Philipp目前正在处理这个问题,您希望能够使用类似InitMessage
的东西,而不是Message
。@Philipp我现在有了最后一个工作示例。我希望这正是您想要的。@Philipp并提供了一个如何从消息中读取输入值的工作示例。这是一种聪明的方法。所有的演员都被隐藏了!谢谢你开发它。尽管如此,我相信Abhinav Sarkar的解决方案甚至更为优越,因为它用处理程序的映射来代替梯形图的实例。我被教导梯形图的实例是代码气味。这就是为什么我在寻找一种更加面向对象的方法。也许是访问者模式?我喜欢这个想法:这更像是一种回调方法。非常干净的解决方案,谢谢!(与斯奇维非常相似)。经过一些调整,我还可以从消息中删除getTypeClass()方法。您可以通过message.getClass().getGenericInterfaces()[0].getActualTypeArguments()访问相同的信息(需要对参数进行一些转换)
api: init
client: init
api: hello
client: hello
if(rsp instanceOf HelloResponse){
HelloResponse hrsp = (HelloResponse)rsp;
...
else if ...
class GenericResponse{
private String message;
private int responseType;
...
}
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);
}
}
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;
}
}
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());
}
}