Skip to content

Conditions to Strategy

December 7, 2011

In response to question to add more detail on my previous post:

Interesting… I am new to programming/scripting (learning bash scripting and some python) and was wondering if you could provide an example of pseudo code showing how the conditional statement can be cleaned up with “strategy or [a] template”?

Is it a method that helps reduce redundant/duplicated code?

In a nutshell:

  • Create abstract base class for problems solved in condition.
  • Create concrete classes for actual problems solved in condition.
  • Create Factory Method class for choosing the right algorithm.
  • Use abstract class where your condition has been.

However I will show how condition statements can be changed to more maintainable and readable code (c++, not python way though…).

Lets say we are working on a scientific project for physics. We would need to solve some simple equations eventually, so that is what we are going to work on now (implementation is not important so lets just focus on design).

First lets have a look at some bad implementations. It was weird to write bad code on purpose and I couldn’t think of how bad should code be for my example. So I will put some examples of design flaws I’ve encounter daily. I’ve been surprised of the variety myself!

Lets take a look at first example (without conditions yet, however they are lurking inside physics class somewhere):

class Equation
{
public:
 Equation(const vector<int>& equationArgs);
 void setSolution(const vector<int>& equationArgs);
 const vector<int> getArgs();
 const vector<int> getSolution();
private:
 vector<int> arguments;
 vector<int> solution;
};
class SomePhysicsClass1
{
public:
 //Some hard physics stuff here that needs equations solved
private:
 //equation solving algorithms
};
class SomePhysicsClass2
{
public:
 //Some other hard stuff here that needs equations solved
private:
 //equation solving algorithms
};

There are numerous things that are wrong with this:

  1. If I want to create another Physics class that needs equation solving, I would have to write code once again, resulting in code duplication.
  2. If I want to make some changes in the way equation is solved or add another algorithm to it, I would have to go around all Physics classes and repeat code.
  3. In this example class Equation works just like a wrapper for vector<int> stuff. Real work in actually solving equations is done in Physics algorithm class. This is very wrong, because we would like to make classes responsible for nothing more than they actually should do.
  4. I can take already solved equation and accidentally change its result — arguments and solution are not tied up together anymore.

You might think this is obvious and no one actually does that, but believe me, with the code I work, there are tons of such classes-wrappers with ALL of their methods being getters and setters. All the real work calculating the variables is done outside of them. Worst, when working with large code base, it gets much more confusing than this example.

Lets improve it a bit and move to another example, I encounter this quite frequently too (example not yet with conditions as well, but they are there somewhere!):

class Equation
{
public:
 Equation(const vector<int>& equationArgs);
 void setSolution(const vector<int>& equationArgs);
 const vector<int> getArgs();
 const vector<int> getSolution();
private:
 vector<int> arguments;
 vector<int> solution;
};
class SolveEquation
{
 //equation is solved here and returned
};
class SomePhysicsClass1
{
 //Some hard physics stuff here that needs equations solved
};
class SomePhysicsClass2
{
 //Some other hard stuff here that needs equations solved
};

This time equation solving is moved to SolveEquation. This removes first two issues, however we still have 3rd and 4th as before. Again as I’ve said, I encounter this on daily basis.

Lets remove 3rd and 4th issue and move to the conditions. In this example solving equation is done inside the class itself:

class Equation
{
public:
 Equation(const vector<int>& equationArgs);
 void solveEquation()
 {
   if (arguments.size() < 3)
   {
     solveLinearEquation();
   }
   else if (arguments.size() == 3)
   {
     solveQuadraticEquation();
   }
 };
 const vector<int> getArgs();
 const vector<int> getSolution();
private:
 solveLinearEquation();
 solveQuadraticEquation();

 vector<int> arguments;
 vector<int> solution;
};

For this small Equation class this is perfectly fine solution. If I need to solve cubing equation, I just add another method and yet another condition, but this is a fine. Actually for splitting it up with some Design Patterns such as Template or Strategy (here I’ll be using Strategy, see the difference in my other post), I would violate KISS principle. But if this would be larger problem (say for example travelling salesman problem), I wouldn’t like to have all those algorithms in one class. Anyway lets split it to Strategy!

class Equation
{
public:
 Equation(const vector<int>& equationArgs);
 virtual void solveEquation() = 0;
 const vector<int> getArgs();
 const vector<int> getSolution();
private:
 vector<int> arguments;
 vector<int> solution;
};
class LinearEquation : public Equation
{
public:
 //constructors, destructors
 void solveEquation(); //solves just linear equations
};
class QuadraticEquation : public Equation
{
public:
 //constructors, destructors
 void solveEquation(); //solves just quadratic equations
};

Now if we need cubic equation solver we just create new class. But how do we avoid if else in physics algorithm classes? We need to employ some Creational Patter, such as Factory Method for example and in physics class we don’t bother with what object to create, just pass the arguments and solve equatian without worrying what it actually is!

Advertisements
2 Comments leave one →
  1. December 11, 2011 06:11

    Cool, in bash these are similar to “functions”. I read they are very C’ish.

    functionName () {
    CodeBlockOrCommand
    }
    functionName #used to call the function later…

    Using functions, I will be able to shorten my scripts considerably and make them easier to maintain or include revisions.

    • December 12, 2011 12:38

      Well, that is not entirely the point I wanted to make… The main point is, that algorithms should fallow their flow and should not be interrupted by side calculations by choosing between if else statements, consider this:

      functionForSomeAlgorithm(){
      //normal algorithm flow
      //”if else” statement to calculate some values based on some integer input value
      ///normal algorithm flow
      }

      now “if else” could be moved to another function, for example:

      calcMyValue(int){
      if (…)

      else if (…)

      else if (…)

      }

      and it might seem that it would not interrupt the algorithm, however it actually does interrupt it. Your algorithm still has to go about all those if else statements.

      For this reason there is a function pointer in C and C++. For example in functionForSomeAlgorithm() declaration you could specify exact algorithm for the value calculation as fallows:

      functionForSomeAlgorithm((*ptrToFunc)(int)){
      //normal algorithm flow
      ptrToFunc(6);
      ///normal algorithm flow
      }

      For example in C++ there are STL algorithms, for example sort function, that uses this thing, you can pass instructions for sort algorithm how do you want your objects to be compared.

      Now this does not eliminate “if else” entirely, you might still have to have it somewhere, but it moves it out of the algorithm and lets it do its job. Of course there are cases, where you cannot simply avoid between choosing the next flow in the algorithm.

      So as you can see, strategy/template approach is not always the right thing to do as well. In software writing you deal with too much different situations for one thing to be right all the time, but my point in the first place was, that if I am working with some algorithm, I really hate to see it to have make decisions that should have been moved out of it or two algorithms that are merged to one function and the one to be actually executed is chosen by IF ELSE (and THAT is when I refactor it with strategy/template patterns).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: