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.

1

3 Answers 3

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.

5
  • Okay, thanks. In addition to it. Would it still be Factory Pattern if AService and BService are singleton and if ServiceResolver looks like this now: class ServiceResolver { pubic static BaseService resolveService(String type) { if(type == "A") return AService.getInstance(); if (type == "B") return BService.getInstance(); } } Commented Oct 21, 2021 at 18:37
  • @NamanGupta I would still consider that a factory method. One thing with patterns to keep in mind is that they are meant to describe things and not tell you how to do things. You can tweak them to your needs and say this is 'like' a factory method for example. A related pattern is the Abstract Factory but it's one that I think has been overused.
    – JimmyJames
    Commented Oct 21, 2021 at 18:41
  • 1
    If you actually go through the Wikipedia article on Factory Method you linked to, you'll see that this is, in fact, not that pattern. The key aspect of Factory Method is that the factory abstraction doesn't know the concrete objects that it'll be creating, as these are supplied by (potentially not yet existing) client code across an architectural boundary; see this. Commented Oct 22, 2021 at 12:17
  • 1
    @FilipMilovanović You are absolutely right. My bad. What do we call this? I want to call it a factory method i.e. a method that is a factory for objects but I agree that it's not 'The Factory Method Pattern'. I kind of feel like the full-blown pattern is somewhat obsolete given contemporary languages and methodologies. You sort of touch on this when you reference lambdas in your linked answer.
    – JimmyJames
    Commented Oct 22, 2021 at 15:19
  • 1
    @JimmyJames I know "factory method" is in common use in this sense. I guess we need to distinguish between the simple(r) factory that is a method/function, and the traditional Factory Method pattern. I usually just say "factory" to avoid confusion, but often as the discussion develops you end up having to make the distinction anyway. So, I don't really have a good solution either, no easy workaround there. Commented Oct 22, 2021 at 15:40
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.

3
  • "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." That would have been true years ago but you can pass references to static methods in Java since 1.8.
    – JimmyJames
    Commented Oct 22, 2021 at 16:13
  • @JimmyJames, sure you can pass function references around and I've adopted using delegates rather than objects in the dependency graph before. But it needs to be done with care as it can result in far too much mocking all too easily as one can end up mocking the entire factory rather than the types it creates, which easily leads to useless tests.
    – David Arno
    Commented Oct 25, 2021 at 8:42
  • 1
    But it is possible to use static methods without tight coupling. For example a static method can be used as an implementation for a single method interface such as Comparator and passed to a e.g. a TreeSet without the TreeSet knowing anything about the static method. IIRC, the static method doesn't even need to be public.
    – JimmyJames
    Commented Oct 25, 2021 at 14:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.