在我的程序中,我通过 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;
}
我不喜欢大型开关结构。我的问题是:是否有一些方法可以重构这段代码以使其更加优雅?也许使用某种模式?
另外,将来我想尝试在我的程序中使用依赖注入(inject)(Guice),它可以用于根据收到的 ID 实例化 ClientRequest
实例吗?
最佳答案
将 ID 映射到响应对象是一项常见任务,但很难摆脱以某种方式枚举哪个 ID 映射到特定响应对象的情况。 switch
您提供了 block 作品,但它不是最具可扩展性的。例如,如果添加新的响应对象或 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
语句)。尽管这看起来可能略有不同,但动态方法使我们能够进一步改进解决方案。
如果我们采用依赖注入(inject)(DI)框架,例如 Spring,我们可以利用 Java 中的一些创造性功能。例如,我们可以添加新的 ClientRequestFactory
通过创建新的 ClientRequestFactory
实例( map 中的新条目)类。例如:
@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
类中创建)。欲了解更多信息,请参阅the Spring Framework Guru's article on Component Scanning .
摘要
- 在某种程度上,ID 为
ClientRequest
必须建立映射 - 在运行时建立映射提供了更多选择
- Spring 可用于解耦创建
ClientRequest
的工厂 bean 之间的依赖关系。对象和ClientRequestSwitchboard
关于java - 基于请求标识符的客户端请求工厂,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50949069/