java - 基于请求标识符的客户端请求工厂
问题描述
在我的程序中,我通过 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),它可以用于ClientRequest
根据收到的 ID 实例化实例吗?
解决方案
将 ID 映射到响应对象是一项常见任务,但很难摆脱以某种方式枚举哪个 ID 映射到特定响应对象。您提供的switch
块有效,但它不是最可扩展的。例如,如果添加了新的响应对象或 ID,则必须将case
语句添加到switch
.
一种替代方法是创建 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(使用Component
或 的其他派生类进行注释Component
,例如Service
,并且这些 bean 存在的目录由组件扫描拾取或在类中显式创建@Configuration
)。有关更多信息,请参阅Spring Framework Guru 关于组件扫描的文章。
概括
- 在某种程度上,
ClientRequest
必须建立映射的 ID - 在运行时建立映射打开了更多选项
- Spring 可用于解耦创建
ClientRequest
对象的工厂 bean 和ClientRequestSwitchboard