基于请求标识符的客户端请求的工厂



在我的程序中,我通过Java套接字从客户端接收请求。每个请求都有一个唯一的命令标识符,该标识符对应于应用程序端的指定命令。

现在我有一个带有非常大开关的类,它根据收到的命令 ID 创建命令类的实例。此类接收来自客户端的请求数据ByteBuffer,以及ClientConnection对象(表示客户端和服务器之间连接的类(。它从ByteBuffer读取前两个字节并获取相应的命令(扩展ClientRequest类的类的实例(。

例如:

public static ClientRequest handle(ByteBuffer data, ClientConnection client) {
int id = data.getShort();  //here we getting command id
switch (id) {
case 1:
return new CM_ACCOUNT_LOGIN(data, client, id);
case 2:
return new CM_ENTER_GAME(data, client, id);
//...... a lot of other commands here
case 1000:
return new CM_EXIT_GAME(data, client, id);
}
//if command unknown - logging it
logUnknownRequest(client, id);
return null;
}

我不喜欢大型开关结构。我的问题是:有没有办法重构这段代码,使其更优雅?也许使用一些模式?

另外,将来我想尝试在我的程序中使用依赖注入(Guice(,它可以用于根据收到的ID实例化ClientRequest实例吗?

将 ID 映射到响应对象是一项常见任务,但很难以某种方式枚举哪个 ID 映射到特定的响应对象。您提供的switch块有效,但它不是最具可扩展性的。例如,如果添加了新的响应对象或 ID,则必须向switch添加case语句。

一种替代方法是创建 ID 到可以创建新的响应对象的工厂对象的映射。例如:

@FunctionalInterface
public interface ClientRequestFactory {
public ClientRequest createClientRequest(ByteBuffer data, ClientConnection client, int id);
}
public class ClientRequestSwitchboard {
private final Map<Integer, ClientRequestFactory> mappings = new HashMap<>();
public ClientRequestSwitchboard() {
mappings.put(1, (data, client, id) -> new CM_ACCOUNT_LOGIN(data, client, id));
mappings.put(2, (data, client, id) -> new CM_ENTER_GAME(data, client, id));
// ... Add each of the remaining request types ...
}
public ClientRequest createClientRequest(ByteBuffer data, ClientConnection client, int id) {
ClientRequestFactory factory = mappings.get(id);
if (factory == null) {
return createDefault(data, client, id);
}
else {
return factory.createClientRequest(data, client, id);
}
}
protected ClientRequest createDefault(ByteBuffer data, ClientConnection client, int id) {
logUnknownRequest(client, id);
return null;
}
}

然后,您可以按如下方式使用这些ClientRequestSwitchboard

private static final ClientRequestSwitchboard switchboard = new ClientRequestSwitchboard();
public static ClientRequest handle(ByteBuffer data, ClientConnection client) {
int id = data.getShort();
return switchboard.createClientRequest(data, client, id);
}

switch技术相比,此方法的优点是,您现在可以将映射信息存储为动态数据,而不是静态case语句。在动态方法中,我们可以在运行时添加或删除映射,而不仅仅是在编译时(通过添加新的case语句(。虽然这看起来略有不同,但动态方法使我们能够进一步改进解决方案。

如果我们使用依赖注入(DI(框架,例如Spring,我们可以利用Java中的一些创造性功能。例如,我们可以通过创建新的ClientRequestFactory类来添加新的ClientRequestFactory实例(映射中的新条目(。例如:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClientRequestFactoryForId {
public int value();
}
@Service
@ClientRequestFactoryForId(1)
public class AccountLoginClientRequestFactory implements ClientRequestFactory {
@Override
public ClientRequest createClientRequest(ByteBuffer data, ClientConnection client, int id) {
new CM_ACCOUNT_LOGIN(data, client, id);
}
}
@Service
public class ClientRequestSwitchboard {
private final Map<Integer, ClientRequestFactory> mappings = new HashMap<>();
private final ListableBeanFactory beanFactory;
@Autowired
public ClientRequestSwitchboard(ListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@PostConstruct
@SuppressWarnings("unchecked")
private void findAllClientRequestFactories() {
Map<String, Object> factories = beanFactory.getBeansWithAnnotation(ClientRequestFactoryForId.class);
for (Object factory: factories.values()) {
int id = dataStore.getClass().getAnnotation(ClientRequestFactoryForId.class).value();
if (factory instanceof ClientRequestFactory) {
mappings.put(id, (ClientRequestFactory) factory);
}
else {
throw new IllegalStateException("Found object annotated as @ClientRequestFactoryForId but was not a ClientRequestFactory instance: " + factory.getClass().getName());
}
}
}
public ClientRequest createClientRequest(ByteBuffer data, ClientConnection client, int id) {
ClientRequestFactory factory = mappings.get(id);
if (factory == null) {
return createDefault(data, client, id);
}
else {
return request.createClientRequest(data, client, id);
}
}
protected ClientRequest createDefault(ByteBuffer data, ClientConnection client, int id) {
logUnknownRequest(client, id);
return null;
}
}

这种技术使用 Spring 来查找具有特定注释的所有类(在本例中为ClientRequestFactoryForId(,并将每个类注册为可以创建ClientRequest对象的工厂。执行类型安全检查,因为我们不知道用ClientRequestFactoryForId注释的对象是否真的实现了ClientRequestFactory,即使我们期望它这样做。要添加新工厂,我们只需创建一个带有ClientRequestFactoryForId注释的新 bean:

@Service
@ClientRequestFactoryForId(2)
public class AccountLoginClientRequestFactory implements ClientRequestFactory {
@Override
public ClientRequest createClientRequest(ByteBuffer data, ClientConnection client, int id) {
new CM_ENTER_GAME(data, client, id);
}
}

此解决方案假定ClientRequestSwitchboard和用ClientRequestFactoryForId注释的每个类都是 Spring 应用程序上下文已知的 bean(使用ComponentComponent的其他派生物(例如Service(进行注释,并且这些 bean 所在的目录由组件扫描拾取或在@Configuration类中显式创建(。有关更多信息,请参阅 Spring Framework Guru 关于组件扫描的文章。


总结

  • 在某种程度上,必须建立 ID 到ClientRequest映射
  • 在运行时建立映射打开了更多选项
  • Spring 可用于解耦创建ClientRequest对象的工厂 bean 与ClientRequestSwitchboard

相关内容

最新更新