Question

I am not clear on what advantage does strategy pattern offers over simple if-else.

Example of poor code

//poor code that we all can agree is not the way to go
----- main class -----
String strategyname;
//lets assume at this point variable strategyname has a value assigned somehow at runtime
if(strategyname == 'algorithm1'){
    //long code for algorithm1
}elsef(strategyname == 'algorithm2'){
    //long code for algorithm2
}

Disadvantage of above code is that if we need to add another algorithms, we touch the main class (which could have other functionalities built into it as well), so that increases the probability of us breaking some other functionality. Plus class starts to become increasingly long. And unit testing becomes tough.

Now ideally, as per java design pattern book goes, we shold use strategy desig pattern. It would go something like this

//strategy design pattern

------ main class -----
algorithmsInterface selectedAlgorithm = algorithmClassFactory.getAlgorithm(strategyname);
selectedAlgorithm.doTheJob(commonParameter);


------ factory class that returns instance of appropriate class ---
class algorithmClassFactory{
    
    public static algorithmsInterface getAlgorithm(String strategyname){
        if(strategyname == 'algorithm1') {
            return new algorithm1Class();
        }elseif(strategyname == 'algorithm2'){
            return new algorithm2Class();
        }
    }
}

------ interface that all algorithms classes need to implement
interface algorithmsInterface{
    public void doTheJob(commonParameter);
}
------ class for algorithm1 -----
class algorithm1Class implements algorithmsInterface{
    public void doTheJob(commonParameter){
        //long code for algorithm1
    }
}
------ class for algorithm2 -----
class algorithm2Class implements algorithmsInterface{
    public void doTheJob(commonParameter){
        //long code for algorithm2
    }
}

I understand we are able to use polymorphism here. Main class doesn't need to be touched when we add another algorithm, as we can just add a new class for it and modify the algorithmClassFactory class to include that class as well. But what advantage does it offer over below code, where we dont use interface, and just directly call the appropriate class from factory class-

//why cant we do as below

------ main class -----
algorithmClassFactory.getAlgorithm(strategyname);

------ factory class that calls the appropriate algorithm class ---
class algorithmClassFactory{
    
    public static algorithmsInterface getAlgorithm(String strategyname){
        if(strategyname == 'algorithm1') {
            algorithm1Class algo1 = new algorithm1Class();
            algo1.doTheJob(commonParamter);
        }elseif(strategyname == 'algorithm2'){
            algorithm1Class algo2 = new algorithm2Class();
            algo2.doTheJob(commonParamter);
        }
    }
}

------ class for algorithm1 -----
class algorithm1Class{
    public void doTheJob(commonParameter){
        //long code for algorithm1
    }
}
------ class for algorithm2 -----
class algorithm2Class{
    public void doTheJob(commonParameter){
        //long code for algorithm2
    }
}

Without the use of interface (polymorphism) i am forced to repeat the line of calling doTheJob method of each class. I guess we can call it a disadvantage. But is this the only advantage Strategy design pattern offers over this last design? With above code, am i violating any OOPS design principle like Separation of Concern, single responsibility, etc? Am i wrong in assuming that even last design follows Open-closed principle?

Was it helpful?

Solution

First of all, you dont return anything in your 3rd example

 public static algorithmsInterface getAlgorithm(String strategyname){
        if(strategyname == 'algorithm1') {
            algorithm1Class algo1 = new algorithm1Class();
            algo1.doTheJob(commonParamter);
        }elseif(strategyname == 'algorithm2'){
            algorithm1Class algo2 = new algorithm2Class();
            algo2.doTheJob(commonParamter);
        }
    }

You claim that you need to write a new line "doTheJob(...)" again and again each time you add a new class of algorithm, and therefore your 2nd example (i.e. the claimed strategy pattern) is superior in this case, but this is wrong as you can refactor it to just call "doTheJob(...)" once at the end of the function as all the algorithms are implementing the same interface like this:

 public static void getAlgorithm(String strategyname){
        algorithmsInterface algo = null;
        if(strategyname == 'algorithm1') {
            algo = new algorithm1Class();
        }elseif(strategyname == 'algorithm2'){
            algo = new algorithm2Class();
        }
       
        algo.doTheJob(commonParamter);
    }

notice also that I changed the return value to void as we don't return anything, you may want to consider changing also the name, as it is no longer retrieves an algorithm.

Strategy pattern doesn't necessary include the usage of the Factory pattern, Strategy pattern is simply a method to implement in practice the Dependency Injection/Inversion principle (the D in SOLID) along with the Open-Closed principle.

It seems that your question is more related to the Factory design pattern rather than the Strategy pattern.

then in this case, Factory pattern simply allows you to "push" the switch-case statement (or in your case, if-else) to be encapsulated into some other class, the switch-case statement would work the same in any situation you have described, although by encapsulating the switch-case statement inside a class, you made it "lazy", in sense that you will use the factory (and hence the switch-case) to instantiate a new algorithm object depending on some information you'll receive later on.

In your given case, it is hard to judge what is the better way to go forward without knowing the context, as they all practically do the same (as of course not taking into consideration some compiler errors as no return value stated above).

Not all design patterns are following all SOLID principles, but they represent well-tested patterns to solve common problems.

Regarding your last question, you may want to see: Does the Factory Pattern violate the Open/Closed Principle?

OTHER TIPS

But what advantage does it offer over below code, where we dont use interface, and just directly call the appropriate class from factory class-

The problem with your example factory is that you just copy/pasted the if/else. But strategy patterns are also relevant in cases where it's not just an if/else. Note that this is not an exercise in pedantry - you could reduce almost all code down to an if/else if you're trying to.

Let's use an example. I have an application in which:

  • People from the US are addressed as Mr/Ms/Mrs
  • People from Mexico are addressed as Senor/Senorita/Senora
  • Any user can override the default cultural address they receive - regardless of their country.
  • Any user can override the which gendered address they receive.

That's no longer a straightforward if/else. That's a complex bit of decision logic going on there. That's complex (and liable to change) enough that it can be abstracted into its own thing.

But the Person class shouldn't be bothered with all that logic. So you abstracting it in a "naming strategy", and have your Person depend on a given naming strategy. What that strategy is and how that strategy is decided, is not something Person cares about.

"But you could still write that as a series of if/else statements"

Like I said, it's not an exercise in pedantry. Any code can mostly be reduced to if/elses but that's not going to improve the code.

But here's an example where you can't even account for it using if/elses: game mods. These are DLLs that are dynamically loaded into a game, post compilation, which contain implementations, extensions and overrides of established structured in the source code.

Since the official game developers could not account for any mod that anyone could create after they release their game, and they're certainly not going to update and re-release their game code every time someone decided to make a mod; this means that the code cannot account for its strategies using a predetermined if/else structure.

Without the use of interface (polymorphism) I am forced to repeat the line of calling doTheJob method of each class. I guess we can call it a disadvantage.

I will assume you didn't underrate the problem intentionally, because I can think in some more disadvantages like:

  • class harder to understand
  • class harder to test
  • class with no Single Responsibility
  • class coupled to concrete classes, so poor abstraction and decoupling, which might lead you (or not, who knows) to break Open-Closed principle eventually.

But the question is -Do all these things matter?-. These are disadvantages only if they are.

I mean, if the method to decouple only has 2 possible execution paths, then I would find reasonable someone defending the if/else but, if at some point the method grows and we think that it will keep growing in the future, then I would not find it reasonable.

Anyways, I don't think that your example is a good example of the Strategy Pattern. It's likely suitable for the Factory Pattern.

if(strategyname == 'algorithm1') {
            return new algorithm1Class();
        }elseif(strategyname == 'algorithm2'){
            return new algorithm2Class();
        }

This is very much what we would expect of any factory or abstract factory. It's a problem at the time of creating new instances. I don't see in this example any evidence for you to implement the Strategy Pattern. What leads me to the next question -Do you know when to use one or another pattern?-.

You first have to identify the problem and then find out the pattern beneath (if any). If there's a pattern (it has a sort of anatomy o structure) and it's catalogued, congratulations, it's likely you will find the implementation to solve the problem. It's going to be documented somewhere or implemented already in one or another library.

But if not, you have to figure out the solution from scratch.

You might find other patterns that fit or do the job too, but their adequacy is arguable. Like the current one (IMO).

Adequacy matters here, because patterns don't tell about solutions, they tell about problems. Calling Strategy to your classes could be confusing at first glance and controversial for dogmatic or experienced developers.

When I see a Factory what first comes to mind is

creating these components involves complexity and such complexity has been delegated to this abstraction.

When I see a Strategy class, what first comes to mind is

these components can behave in different ways and such behaviours are dynamic and/or configurable.

Licensed under: CC-BY-SA with attribution
scroll top