设计模式:避免切换决定哪个服务调用

时间:2016-08-05 22:05:56

标签: java design-patterns strategy-pattern

对于项目,我们有一个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决定调用哪个服务实现,从而将合作伙伴的切换放在一个地方。

我使用了“策略”一词,因为我被告知这种设计模式可以实现,但我不确定使用它的最佳方式,或者是否有更好的方法来解决这个问题。

编辑:我已经更新了这个问题,因为术语提供商会产生误导。我在输入表单中的内容是我们执行请求的合作伙伴的名称。我想要一个模式,根据表单中的合作伙伴名称决定哪个是正确的实现(几个服务中的哪一个)

4 个答案:

答案 0 :(得分:2)

一般来说,表格不应该知道什么是"提供商"将要处理它。相反,提供者应该能够解释他们可以处理哪种输入。

我建议使用一种形式的责任链(将重构条件替换为多态性),它看起来像这样(为简单起见,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

答案 1 :(得分:0)

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

在这里,您必须从 InputForm 获取指定的 ServiceProvider

你可以拥有策略大师类这样的东西。

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。

另外一件事是,当你提到一个模式时,总是试图全面了解。不要严格查看任何源提供的实现示例。永远记住它是 实施,不是实施。

答案 2 :(得分:0)

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

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,将查找逻辑移至此课程,并让MyController使用ProvicerServiceLocator

有关使用标准java的服务提供者接口和服务提供者查找的详细说明和示例代码,请参阅我的博客A plug-in architecture implemented with java

答案 3 :(得分:0)

混合来自不同答案的想法我来了

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 注册了不同的合作伙伴&#39;服务提供商,它实现代码中唯一需要的交换机,并为控制器中使用的正确伙伴返回正确的服务

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

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