luso luso - 3 months ago 14
Java Question

Design pattern: avoid switch to decide which service call

For a project, we have a Controller/Service/DAO architecture. We implement calls to different providers' APIs so we ended up with some boilerplate code like this in every controller 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;
}
}
}


Which design pattern can I use to get rid of this switch in every controller? I would prefer the code to be something like the below:

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())
}
}


letting the serviceStrategy decide which service implementation to be called, and thus having the partner's switch in a single place.

I've used the term "strategy" because I've been told this design pattern could make it, but I'm not sure of the best way to use it or if there is a better approach to solve this problem.

EDIT: I've updated the question as the term provider is misleading. What I have in the input form is the name of the partner for which we do the request. I want a pattern that decides which is the correct implementation (which one of the several services) to use based on the partner's name in the form

Answer

Mixing ideas from different answers I came up to

ServiceProvider.java A superclass for all the service providers. Contains a map of the different services for each partner

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 there is a service provider for each partner, which are injected with the actual service classes for the different methods.

@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 Injected with the different partners' service providers, it implements the only switch needed in the code and returns the correct service for the correct partner to be used in the controller

@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 finally, in my controllers, I just need to inject the serviceStrategy class and use it to recover the correct service.

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

So, switch off! Hope this helps someone