在服务器代码中,我有一个ProtocolHandler
类,它从套接字读取,找出它处理的数据包类型并将其发送给客户端。我试图拥有以下架构:
public interface Packet {
//...
}
public class ClientInformations implements Packet {
//...
}
public class ProtocolHandler {
//....
public void bytesReceived(byte[] bytes) {
//... Determine the type of the packet
Packet packet = determineTypeOfPacketAndRead(bytes);
// Here I already have the packet object built,
// like ClientInformations@1a1a1a1a[...]
client.packetReceived(packet);
}
//...
}
public class Client {
//...
public void packetReceived(Packet pkt) {
System.out.println("Unimplemented packet received.");
}
public void packetReceived(ClientInformations ci) {
}
//...
}
(我不知道如何用文字轻松解释)。问题是,我真的认为packetReceived(ClientInformations)
会被调用,但事实并非如此。它调用了更通用的packetReceived(Packet)
方法。我错了吗?那我怎么还能使用相同的架构呢?
- 编辑
现在我明白为什么会这样。问题是,我有其他数据包类,例如Movement
,Sync
,Spawn
。我想通过向Client
类添加新方法来轻松添加新数据包。那不是另一种做法吗?我的意思是,一种自动分析运行时packet
对象的类型并进行最具体的方法调用的方法?
答案 0 :(得分:5)
我假设你有Packet packet = determineTYpeOfPacketAndRead(bytes)
,因为代码几乎没有其他方式可以编译。
所以引用类型是Packet
(在编译时),即使对象类型是ClientInformations
(在运行时)。
并在Java语言规范第15章中指定方法重载。
在编译时选择最具体的方法;它的描述符决定了在运行时实际执行的方法。
更新:您的代码目前正常运行,因为您的代码中没有歧义,即使它的行为不符合您的要求。但是,您将无法扩展此代码,因为它会导致问题,让我说明一下:
我之前提出的建议如下:
public class Client {
//...
public void unknownPacketReceived(Packet pkt) {
System.out.println("Unimplemented packet received.");
}
public void packetReceived(ClientInformations ci) {
}
//...
}
然而,这不能起作用,因为ClientInformations
对象是一个更简单的Packet
对象/接口,它会产生以下编译错误:
incompatible types: Packet cannot be converted to ClientInformations
当引用类型仍为Packet
时,会发生这种情况,而您希望将其提升为ClientInformations
。
如果您将其称为:
,它可以工作client.receivedPacket((ClientInformations)packet)
这对你的情况没有任何害处,因为在运行时它们属于同一类型,因此转换中没有任何东西丢失,这需要伴随instanceof
树,这就是你回来的原因现在是零步。
这个程序只是才能正常工作,如果你希望它变得简单,我建议改变你的设计,继续这条路径可能是一个选项,如果您正在处理遗留应用程序,并且不能只是更改内容,但这并不容易。
答案 1 :(得分:1)
问题是,如果我理解正确的话,你传递的对象都是Packet
和ClientInformations
,所以这两种方法都没有优先于其他方法。
只需给另一个方法另一个名称,你的问题就会解决。
答案 2 :(得分:1)
您必须按照以下方式执行此操作:
public void bytesReceived(byte[] bytes) {
//... Determine the type of the packet
packet = determineTypeOfPacketAndRead(bytes);
// Here I already have the packet object built,
// like ClientInformations@1a1a1a1a[...]
if (packet instanceof ClientInformations) {
ClientInformations ci = (ClientInformations)packet;
client.packetReceived(ci); // this way, it's clear which method you want to call
}
}
但是,我更喜欢Tim B的建议来改变方法名称。我建议clientInformationsPacketReceived()
。
答案 3 :(得分:1)
Java是一种单一调度语言,所以诀窍是学会利用它来达到最佳效果。
在您的情况下,您不需要在Client
类型上发送,但您确实需要Packet
类型。让此要求指导您的设计:不要将方法重载添加到Client
,而是让数据包包含像
void handle(Client c)
并将客户端的所有内容都提供给Packet
。这将使您有机会在Packet
的每个专业化中提供不同的处理。
不幸的是,调度的类型必须是拥有相关方法的类型,这可能会损害您分离问题的需要。这可以通过更多的架构来实现,但我希望这个指南可以帮助你。
答案 4 :(得分:1)
为什么的答案已在其他答案中显示。一个解决方案(据我所知)需要改变你的思维过程。
现在你正在尝试将逻辑放在某种通用类中来处理数据包,该类可以处理每种类型的数据包。这是一种相当常见的反模式:创建“管理器”类,为特定对象完成工作。
一个简单的解决方案就是将这个逻辑简单地放在你的课程中。
当您遇到以下情况时
interface Packet {
void packetReceived();
}
class ClientInformations implements Packet {
@Override
public void packetReceived() {
System.out.println("I am ClientInformations");
}
}
您可以在变量packetReceived
上致电packet
,这样就可以为您完成工作。
答案 5 :(得分:0)
您可以typecast
您的对象引用来调用packetReceived(ClientInformations)
而不是packetReceived(Packet)
。
你可以这样打电话client.packetReceived((ClientInformations)packet);