[英]Design pattern: avoid switch to decide which service call
對於項目,我們有一個Controller / Service / DAO架構。 我們實現了對不同提供者API的調用,因此我們在每個控制器類中都得到了一些這樣的樣板代碼:
enum {
PARTNER_A, PARTNER_B, PARTNER_C
}
public class MyController {
@Resource PartnerASearchService partnerASearchService;
@Resource PartnerBSearchService partnerBSearchService;
@Resource PartnerCSearchService partnerCSearchService;
public search(InputForm form) {
switch(form.getPartnerName()) {
case PARTNER_A: partnerASearchService.search();
break;
case PARTNER_B: partnerBSearchService.search();
break;
case PARTNER_C: partnerCSearchService.search();
break;
}
}
public otherMethod(InputForm form) {
switch(form.getProvider()) {
case PARTNER_A: partnerAOtherService.otherMethod();
break;
case PARTNER_B: partnerBOtherService.otherMethod();
break;
case PARTNER_C: partnerCOtherService.otherMethod();
break;
}
}
}
我可以使用哪種設計模式來擺脫每個控制器中的這個開關? 我希望代碼類似於下面的代碼:
public class MyController {
@Resource ServiceStrategy serviceStrategy;
public search(InputForm form){
serviceStrategy.search(form.getPartnerName())
// or
serviceStrategy.invoke(SEARCH, form.getPartnerName())
}
public otherMethod(InputForm form){
serviceStrategy.other(form.getPartnerName())
// or
serviceStrategy.invoke(OTHER, form.getPartnerName())
}
}
讓serviceStrategy決定調用哪個服務實現,從而將伙伴的開關放在一個地方。
我已經使用了“策略”一詞,因為我被告知這種設計模式可以實現,但我不確定使用它的最佳方式,或者是否有更好的方法來解決這個問題。
編輯:我已經更新了問題,因為術語提供者具有誤導性。 我在輸入表單中的內容是我們執行請求的合作伙伴的名稱。 我想要一個模式,根據表單中的合作伙伴名稱決定使用哪個正確的實現(幾個服務中的哪一個)
通常,表單不應該知道“提供者”將處理它的內容。 相反,提供者應該能夠解釋他們可以處理哪種輸入。
我建議使用一種形式的責任鏈(將重構條件替換為多態性),它看起來像這樣(為簡單起見,Groovy):
interface ProviderService {
boolean accepts(InputForm form)
void invoke(String foo, InputForm form)
void other(InputForm form)
}
ProviderService
實現的每個實現都accepts
以指示它是否可以處理特定表單,並且您的控制器存儲List<ProviderService> services
而不是單個引用。 然后,當您需要處理表單時,您可以使用:
ProviderService service = services.find { it.accepts(form) }
// error handling if no service found
service.other(form)
有關在主框架中使用的此模式的綜合示例,請參閱Spring轉換服務 。
實際上,您可以在此處使用策略模式。 它看起來像這樣的東西。
在這里,您必須從InputForm獲取指定的ServiceProvider 。
你可以讓StrategyMaker類像這樣。
Public class StrategyMaker{
public SeriveProvider getProviderStrategy(InputForm inputForm){
return inputForm.getProvider();
}
}
在內部控制器中,你可以做這樣的事情。
public class MyController{
StrategyMaker strategyMaker;
public search(InputForm form){
strategyMaker.getProviderStategy(form).search();
}
}
如果您事先知道所有提供者策略的列表,這將是一個理想的解決方案。 當列表不斷增長時,策略模式無法保持Open-Close-Princple。
另外一件事是當你提到一個模式時,總是試圖弄清楚大局。 不要嚴格查看任何源提供的實現示例。 永遠記住它是一個實現, 而不是實現。
首先通過將提取程序查找代碼提取到自己的方法來刪除它。
public class MyController {
@Resource
ProviderService searchServiceProvider1;
@Resource
ProviderService searchServiceProvider2;
@Resource
ProviderService searchServiceProvider3;
public void search(InputForm form) {
String provider = form.getProvider();
ProviderService providerService = lookupServiceProvider(provider);
providerService.search();
}
public void other(InputForm form) {
String provider = form.getProvider();
ProviderService providerService = lookupServiceProvider(provider);
providerService.other();
}
private ProviderService lookupServiceProvider(String provider) {
ProviderService targetService;
switch (provider) {
case PROVIDER_1:
targetService = searchServiceProvider1;
break;
case PROVIDER_2:
targetService = searchServiceProvider2;
break;
case PROVIDER_3:
targetService = searchServiceProvider3;
break;
default:
throw new IllegalStateException("No Such Service Provider");
}
return targetService;
}
}
至少你可以改進lookupServiceProvider
方法並使用map來避免切換。
private Map<String, ProviderService> providerLookupTable;
private Map<String, ProviderService> getProviderLookupTable(){
if(providerLookupTable == null){
providerLookupTable = new HashMap<String, ProviderService>();
providerLookupTable.put(PROVIDER_1, searchServiceProvider1);
providerLookupTable.put(PROVIDER_2, searchServiceProvider2);
providerLookupTable.put(PROVIDER_3, searchServiceProvider3);
}
return providerLookupTable;
}
private ProviderService lookupServiceProvider(String provider) {
Map<String, ProviderService> providerLookupTable = getProviderLookupTable();
ProviderService targetService = providerLookupTable.get(provider);
if(targetService == null){
throw new IllegalStateException("No Such Service Provider");
}
return targetService;
}
最后,您將認識到可以引入ProviderServiceLocator
,將查找邏輯移動到此類,並讓MyController
使用ProvicerServiceLocator
。
有關使用標准java的服務提供者接口和服務提供者查找的詳細說明和示例代碼可以在我的博客中找到使用java實現的插件體系結構 。
合作伙伴不經常更改時的一種可能解決方案:
class ServiceFactory {
@Resource PartnerService partnerASearchService;
@Resource PartnerService partnerBSearchService;
@Resource PartnerService partnerCSearchService;
public static PartnerService getService(String partnerName){
switch(partnerName) {
case PARTNER_A: return partnerASearchService;
case PARTNER_B: return partnerBSearchService;
case PARTNER_C: return partnerCSearchService;
}
}
public class MyController {
@Resource ServiceFactory serviceFactory;
public search(InputForm form) {
serviceFactory.getService(form.getProvider()).search()
}
public otherMethod(InputForm form) {
serviceFactory.getService(form.getProvider()).otherMethod()
}
}
混合來自不同答案的想法我想到了
ServiceProvider.java所有服務提供者的超類。 包含每個合作伙伴的不同服務的地圖
public abstract class ServiceProvider implements IServiceProvider {
private final Map<ServiceType, IService> serviceMap;
protected ServiceProvider() {
this.serviceMap = new HashMap<>(0);
}
protected void addService(ServiceType serviceType, IService service) {
serviceMap.put(serviceType, service);
}
public IService getService(ServiceType servicetype, PartnerType partnerType) throws ServiceNotImplementedException {
try {
return this.serviceMap.get(serviceType);
} catch (Exception e) {
throw new ServiceNotImplementedException("Not implemented");
}
}
}
ServiceProviderPartnerA.java為每個合作伙伴提供了一個服務提供程序,它們為不同的方法注入了實際的服務類。
@Service("serviceProviderPartnerA")
public class ServiceProviderPartnerA extends ServiceProvider {
@Resource(name = "partnerASearchService")
private ISearchService partnerASearchService;
@Resource(name = "partnerABookingService")
private IBookingService partnerABookingService;
@PostConstruct
public void init() {
super.addService(ServiceType.SEARCH, partnerASearchService);
super.addService(ServiceType.BOOKING, partnerABookingService);
}
}
ServiceStrategy.java注入了不同合作伙伴的服務提供商,它實現了代碼中唯一需要的交換機,並為正確的伙伴返回正確的服務,以便在控制器中使用
@Service("serviceStrategy")
public class ServiceStrategy implements IServiceStrategy {
@Resource(name = "serviceProviderPartnerA")
IServiceProvider serviceProviderPartnerA;
@Resource(name = "serviceProviderPartnerB")
IServiceProvider serviceProviderPartnerB;
@Resource(name = "serviceProviderPartnerC")
IServiceProvider serviceProviderPartnerC;
public IService getService(ServiceType serviceType, PartnerType partnerType) throws PartnerNotImplementedException {
switch (partnerType) {
case PARTNER_A:
return serviceProviderPartnerA.getService(serviceType, partnerType);
case PARTNER_B:
return serviceProviderPartnerB.getService(serviceType, partnerType);
case PARTNER_C:
return serviceProviderPartnerC.getService(serviceType, partnerType);
default:
throw new PartnerNotImplementedException();
}
}
}
SearchController.java最后,在我的控制器中,我只需要注入serviceStrategy類並使用它來恢復正確的服務。
@Resource(name = "serviceStrategy")
IServiceStrategy serviceStrategy;
@RequestMapping(value = "/search", method = RequestMethod.GET, produces = "text/html")
@ResponseBody
public String search(@RequestParam(value = "partner", required = true) String partnerType, String... params) {
ISearchService service = (ISearchService) serviceStrategy.getService(ServiceType.SEARCH, partnerType);
return service.search(params);
}
所以,關掉! 希望這有助於某人
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.