在我的程序中,我正在通过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
实例吗?
答案 0 :(得分:2)
将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
或{ {1}}(例如Component
)和这些bean所在的目录由组件扫描获取或在Service
类中显式创建)。有关更多信息,请参见the Spring Framework Guru's article on Component Scanning。
摘要
@Configuration
的ID映射ClientRequest
对象的工厂bean与ClientRequest
之间的依赖关系