问题描述
我有一个使用泛型的 HTTP 客户端类。我想创建一个带有 (key,value) => (String type,HttpClient>) 映射的工厂类。
HttpClient
类有一个函数,它只根据泛型类类型将数据发送到服务器。基本上我想要的是一个发送数据的通用类,就是这样。
问题是我有多个类,我想使用工厂类以便:
- 简化 HttpClient 对象的创建。
- 避免一直使用“new”关键字,因为我可以在整个应用程序中使用
HttpClient<Some Object>
类的相同实例。
请看下面的代码:
工厂
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);
}
}
SystemPreferencesFactory.getPreferencesHTTPClient(PreferencesHTTPType.DUT_PREFERENCES.getName())
.put(converterUtil.DutFromEntityToDTO(dp));
编译错误是:
required type:
capture of ? extends ISystemPreferences
Provided:
DutPreferencesDTO
我的 DutPreferencesDTO 类声明如下:
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的类,而不是String
中的getPreferencesHTTPClient
,如下所示:
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 类型时,我们不需要显式推断来得到编译错误。