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

标签 java design-patterns dependency-injection refactoring

在我的程序中,我通过 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(用 ComponentComponent 的其他派生注解,例如 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/

相关文章:

java - 在不创建 2 个 guice 帮助程序接口(interface)的情况下使用自定义参数注入(inject)变量?

Java - System.currentTimeMillis();不退差价

android - 安卓设计模式

java - 如何在文本 block 中处理意图(Java 13)

c# - 最佳实践?等到收到或在收到时引发事件

java - spring 中的单例可以有静态方法吗?

node.js - 带有 MongoDB 和 NestJsxAutomapper 的 NestJS 导致错误 'cannot read property plugin of undefined'

c# - 在 ASP .Net 单例注入(inject)类中使用 DbContext

Java - 遍历数组列表并输出从最高到最低的数字?

java - 简单的 GridLayout 缩放动画