2
abstract class BaseService {
 public void doSomething();
}  


class AService extends BaseService {
  public void doSomething(){
   // Do something...
  }
}

class BService extends BaseService {
  public void doSomething(){
   // Do something...
  }
}

class ServiceResolver {
 pubic static BaseService resolveService(String type) {
  if(type == "A") return new AService();
  if (type == "B") return new BService();
 }
}

Usage:
BaseService resolvedService = ServiceResolver.resolveService(type);
resolvedService.doSomething();

What do we call the above pattern? Is it the same as the strategy pattern? I feel it is different because in the strategy pattern client chooses the type of algorithm (https://refactoring.guru/design-patterns/strategy) and passes it to the context class for execution and change the behavior of the object (Context class object) at runtime but in my above example, we are not doing any such thing.

Also, in the extension of the above code:

class ServiceResolver {

@Autowired AService aService;

@Autowired BService bService;

pubic static BaseService resolveService(String type) { if(type == "A") return aService; if (type == "B") return bService; } }

Broadly looking it looks the same as the first one.

3 Answers3

3

I initially called this the Factory Method pattern but as noted by Filip, this is not correct. It's really just a method that acts as an object factory. I can see why you might think it's related to the Strategy Pattern but in a strategy, the change in the underlying implementation object is done within the strategy object. In this case the intent is different in that you are creating these objects and handing them off to other parts of the system to manage.

The kind of thing you are doing here is a useful and simple way to remove explicit dependencies on concrete classes and their constructors. If it meets your needs, I see no reason to overcomplicate things by implementing a more formal Factory Method Pattern.

JimmyJames supports Canada
  • 30,578
  • 3
  • 59
  • 108
1

On its own, this is just an application of polymorphism, but you could also look at this as an incomplete Strategy pattern, with a couple of extras thrown in:

// abstract strategy type
abstract class BaseService { 
  public void doSomething();
}

// concrete strategy A class AService extends BaseService { public void doSomething(){ ... } }

// concrete strategy B class BService extends BaseService { public void doSomething(){ ... } }

// A factory (not an Abstract Factory, not a Factory Method) // acting as a Service Locator ("service resolver" basically means the same thing) class ServiceResolver { pubic static BaseService resolveService(String type) { if(type == "A") return new AService(); if (type == "B") return new BService(); } }

A factory is just some function (or an object) that can create other objects; it's basically a more involved alternative to constructors (it's a "glorified constructor").

The Abstract Factory and Factory Method patterns are specialized kinds of factories, designed to solve more specialized kinds of creation-related problems. People will sometimes call a simple factory that is a function a factory function or a factory method, but that is different.

A Service Locator is a global (or a fairly global) object that you can ask for dependencies; it is generally considered an antipattern (as opposed to explicit Dependency Injection), primarily because of its global nature (you can call it from (more or less) anywhere), and because of the fact that it makes dependencies implicit (dependencies aren't reflected in the constructor or in the interface).

What's missing to your Almost-Strategy is an explicit Context object:

// This is the code that selects the strategy. 
// So, it could be seen as the client (or a part of it). 
// It doesn't matter that it delegates the job to the service locator
BaseService resolvedService = ServiceResolver.resolveService(type);

// This is the "guts" (what would be the internal details) of your nonexistent Context resolvedService.doSomething();

Now, the whole idea of the Strategy pattern is that you can create these preconfigured Context instances and pass them around as objects. And potentially sometimes change the strategy they use dynamically (or not, you can design it either way).

So, something like:

// The Context
class Doer {
  pubic void doTheThing() {
     /* some higher-level logic/algorithm */
     // step 1
     // step 2
     // ...
 // step i - varies - delegate to the strategy:
 this.resolvedService.doSomething();

 // ...
 // step N

}

// maybe some other methods... }

// ----------------------------- // In the client: Doer doer = new Doer(ServiceResolver.resolveService(type));

// call it or send it on its way

If your client and/or context is very simple, or even if it isn't, but you haven't recognized as its own separate thing, and haven't gone through the refactor step to extract it out, and make it more explicit and better defined in code, you're not going to have an obvious Context object. And in fact, as the development progresses, you might, by inertia, repeat roughly the same code in several places, before you realize that these could be DRY-ed out by having instead several Context instances preconfigured with different strategies.

0

In your first code example, you have a factory, but it is a factory with problems. It's a static method, so any code that uses this factory is tightly coupled to this one implementation. Secondly, the method itself is tightly coupled to concrete implementations: AService and BService. All this tight coupling makes testing of this code extremely difficult. Static factories are one of those factories that flirts with being an anti-pattern. If the objects it serves up and deterministic and side effect-free, then they are fine, but it's unlikely that a service fits that bill so this approach can be seen as a bad one here.

Your second code example, you appear to be injecting aService and bService, which is good. Your factory has improved. But you have coupled those two items to concrete types, AService and BService, which is bad as the dependency injection is adding complication without providing much benefit beyond removing the "new is glue" coupling issue of the first example. You've reduced your coupling, but still have some. This is an example of cargo culting, unfortunately. Those types need replacing with abstract classes or interfaces, depending on what your language supports.

But then things get odd. resolveService is still static, but it accesses the instance members, aService and bService. I don't see how that compiles. Is that a typo?

In your comment to JimmyJames, you then introduce the code, AService.getInstance(). This is an example of the singleton pattern. It is just a glorified global variable and so is an absolute anti-pattern. Singletons should be handled solely by injecting them via DI.

David Arno
  • 39,599
  • 9
  • 94
  • 129