Java - 通用工厂 Class
Java - Generic Factory Class
我有一个使用泛型的 HTTP 客户端 class。我想创建一个工厂 class,它有一个带有 (key, value) => (String type, HttpClient>) 的映射。
HttpClient
class 有一个函数,它根据通用 class 类型向服务器发送数据。本质上我想要的是一个发送数据的通用 class,仅此而已。
问题是我有多个 classes,我想使用工厂 class 以便:
- 简化 HttpClient 对象的创建。
- 避免一直使用“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 类型时,我们不需要显式推断来得到编译错误。
我有一个使用泛型的 HTTP 客户端 class。我想创建一个工厂 class,它有一个带有 (key, value) => (String type, HttpClient>) 的映射。
HttpClient
class 有一个函数,它根据通用 class 类型向服务器发送数据。本质上我想要的是一个发送数据的通用 class,仅此而已。
问题是我有多个 classes,我想使用工厂 class 以便:
- 简化 HttpClient 对象的创建。
- 避免一直使用“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 类型时,我们不需要显式推断来得到编译错误。