Java - 通用工厂 Class

Java - Generic Factory Class

我有一个使用泛型的 HTTP 客户端 class。我想创建一个工厂 class,它有一个带有 (key, value) => (String type, HttpClient) 的映射。

HttpClient class 有一个函数,它根据通用 class 类型向服务器发送数据。本质上我想要的是一个发送数据的通用 class,仅此而已。

问题是我有多个 classes,我想使用工厂 class 以便:

  1. 简化 HttpClient 对象的创建。
  2. 避免一直使用“new”关键字,因为我可以在整个应用程序中使用 HttpClient<Some Object> class 的同一个实例。

请看下面的代码:

工厂

public class SystemPreferencesFactory {

    private static SystemPreferencesFactory factory = null;
    private  Map<String, PreferencesHTTPClient<? extends ISystemPreferences>> preferencesMap;

    private SystemPreferencesFactory(){
        this.preferencesMap = Map.of
                (PreferencesHTTPType.DUT_PREFERENCES.getName(), new PreferencesHTTPClient<DutPreferencesDTO>(PreferencesHTTPType.DUT_PREFERENCES.getUrl()),
                PreferencesHTTPType.MACHINE_PROPERTIES.getName(), new PreferencesHTTPClient<MachineProperties>(PreferencesHTTPType.MACHINE_PROPERTIES.getUrl())
                );
    }

    public static PreferencesHTTPClient<? extends ISystemPreferences> getPreferencesHTTPClient(String type) {
        if (factory == null) {
            factory = new SystemPreferencesFactory();
        }
        return factory.preferencesMap.get(type);
    }

}

HttpClient

public class PreferencesHTTPClient<T> extends HTTPClient {
    private static final Logger logger = LoggerManager.getLogger();
    private static String route = "";

    public PreferencesHTTPClient(String route){
        this.route = route;
    }

    public Future<HttpResponse> put(T dto) {
        try {
            System.err.println(dto.getClass());
            HttpPut request = new HttpPut(URIAffix + route + "/" + SystemPreferences.systemPreferences().getSetupName());
            request.addHeader("Authorization", authorizationPassword);
            request.addHeader("Content-Type","application/json");
            request.setEntity(new StringEntity(objectMapper.writeValueAsString(dto)));

            return getClient().execute(request,
                    new FutureCallback<>() {
                        @Override
                        public void completed(final HttpResponse response) {
                            logger.info("update request succeeded");
                        }

                        @Override
                        public void failed(Exception ex) {
                            logger.error("update request failed: {}", ex.getMessage());
                        }

                        @Override
                        public void cancelled() {
                            logger.error("update request canceled");
                        }
                    });
        } catch (JsonProcessingException | UnsupportedEncodingException e) {
            logger.error("update request failed: {}", e.getMessage());
        }

        return CompletableFuture.completedFuture(null);
    }
}

当我尝试在 main 中调用它时出现编译错误

SystemPreferencesFactory.getPreferencesHTTPClient(PreferencesHTTPType.DUT_PREFERENCES.getName())
                 .put(converterUtil.DutFromEntityToDTO(dp));

编译错误为:

Required type:
capture of ? extends ISystemPreferences
Provided:
DutPreferencesDTO

我的 DutPreferencesDTO class 声明如下:

public class DutPreferencesDTO implements ISystemPreferences

我的语法有什么问题?

我们可以修改getPreferencesHTTPClient如下:

public class SystemPreferencesFactory {

...
                  // Change to generic method
    public static <T extends ISystemPreferences> PreferencesHTTPClient<T> getPreferencesHTTPClient(String type) {
        if (factory == null) {
            factory = new SystemPreferencesFactory();
        }
        // cast result explicitly
        return (PreferencesHTTPClient<T>) factory.preferencesMap.get(type);
    }

    public static void main(String[] args) {
        SystemPreferencesFactory.getPreferencesHTTPClient(PreferencesHTTPType.DUT_PREFERENCES.name())
                .put(new DutPreferencesDTO());
        // Oops wrong dto still compiles
        SystemPreferencesFactory.getPreferencesHTTPClient(PreferencesHTTPType.DUT_PREFERENCES.name())
                .put(new MachineProperties());
        // Assign to a local variable to infer
        PreferencesHTTPClient<DutPreferencesDTO> preferencesHTTPClient = SystemPreferencesFactory.getPreferencesHTTPClient(PreferencesHTTPType.DUT_PREFERENCES.name());
        preferencesHTTPClient.put(new DutPreferencesDTO());
        // Compile error;
        preferencesHTTPClient.put(new MachineProperties());
        // Use type witness to guard
        SystemPreferencesFactory
                .<DutPreferencesDTO>getPreferencesHTTPClient(PreferencesHTTPType.DUT_PREFERENCES.name())
                // Compile error;
                .put(new MachineProperties());
    }
}

好像还不错。 但是正如在 main 方法中所展示的那样,有一个缺点。

我们需要在调用 getPreferencesHTTPClient 显式推断 类型,以获得所需的类型检查。但是其他使用此方法的人可能很容易忘记这样做,并且在调用 put 方法时可能会输入错误的 DTO 类型。

所以为了避免这样的问题,我们使用DTO的Class,而不是getPreferencesHTTPClient中的String,如下:

public class SystemPreferencesFactorySafe {
    private static SystemPreferencesFactorySafe factory = null;
    private Map<Class<?>, PreferencesHTTPClient<? extends ISystemPreferences>> preferencesMap;

    private SystemPreferencesFactorySafe() {
        this.preferencesMap = Map.of
                (DutPreferencesDTO.class, new PreferencesHTTPClient<DutPreferencesDTO>(PreferencesHTTPType.DUT_PREFERENCES.getUrl()),
                MachineProperties.class, new PreferencesHTTPClient<MachineProperties>(PreferencesHTTPType.MACHINE_PROPERTIES.getUrl())
                );
    }
                  // T is inferred by type this time
    public static <T extends ISystemPreferences> PreferencesHTTPClient<T> getPreferencesHTTPClient(Class<T> type) {
        if (factory == null) {
            factory = new SystemPreferencesFactorySafe();
        }
        return (PreferencesHTTPClient<T>) factory.preferencesMap.get(type);
    }

    public static void main(String[] args) {
        SystemPreferencesFactorySafe.getPreferencesHTTPClient(DutPreferencesDTO.class)
                .put(new DutPreferencesDTO());
        // Good we see compile error.
        SystemPreferencesFactorySafe.getPreferencesHTTPClient(DutPreferencesDTO.class)
                .put(new MachineProperties());
    }
}

由于类型是使用参数推断的,因此当有人输入错误的 DTO 类型时,我们不需要显式推断来得到编译错误。