设计模式:避免switch来决定哪个服务调用

Design pattern: avoid switch to decide which service call

对于一个项目,我们有一个 Controller/Service/DAO 架构。我们实现了对不同提供商的 API 的调用,因此我们在每个控制器中都得到了一些像这样的样板代码 class:

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 决定调用哪个服务实现,从而将合作伙伴的开关放在一个地方。

我使用术语 "strategy" 是因为有人告诉我这种设计模式可以实现,但我不确定使用它的最佳方式或者是否有更好的方法解决这个问题。

编辑: 我已经更新了问题,因为提供者一词具有误导性。我在输入表单中输入的是我们为其提出请求的合作伙伴的名称。我想要一种模式,它根据表单中合作伙伴的名称来决定使用哪个是正确的实现(几种服务中的一种)

一般来说,表单不需要任何关于 "provider" 将要处理的内容的知识。相反,提供商应该能够解释他们可以处理哪些类型的输入。

我建议使用一种形式的责任链(将重构 Replace Conditional with Polymorphism 合并),看起来像这样(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)

有关主要框架中使用的此模式的综合示例,请参阅 the Spring conversion service

确实,您可以在此处使用 Strategy 模式。它看起来像这样。

这里需要从InputForm.

中获取指定的ServiceProvider

你可以有 StrategyMaker class 这样的东西。

Public class StrategyMaker{

    public SeriveProvider getProviderStrategy(InputForm inputForm){
        return inputForm.getProvider();
    }
}

在控制器内部你可以做这样的事情。

public class MyController{
    StrategyMaker strategyMaker;

    public search(InputForm form){
        strategyMaker.getProviderStategy(form).search();
    }
}

如果您预先知道所有提供商策略的列表,这将是一个理想的解决方案。当列表不断增长时,策略模式无法保持开闭原则。

另一件事是,当您参考模式时,请始终尝试了解全局。不要死板地查看任何来源提供的实现示例。永远记住它是 实现,不是 实现。

首先通过将提供程序查找代码提取到自己的方法中来删除重复的提供程序查找代码。

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方法,使用地图来避免切换。

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,将查找逻辑移动到这个class并让MyController使用ProvicerServiceLocator

可以在我的博客 A plug-in architecture implemented with 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超级class所有服务商。包含每个合作伙伴的不同服务地图

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 每个合作伙伴都有一个服务提供者,为不同的方法注入实际服务 classes。

@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注入了不同合作伙伴的服务提供商,它实现了代码中唯一需要的开关和returns正确的服务在控制器中使用的合作伙伴

@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 class 并使用它来恢复正确的服务。

@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);
}

所以,关掉!希望这对某人有帮助