Skip to content

Execute, don’t Factor

February 20, 2012

Design Patterns are great, however what I really dislike about them is the “Design Pattern Driven Development”. I’ve heard such things as “programming is all about patterns” and “every solution should be based on design patterns”. While using patterns is great, studying them should not lead to conclusion, that solving any situation by the book is a great thing. It will without doubt lead to some really bizarre solutions. Also, even if the SOLID approach is truly a solid base for writing good software, it should be not be looked as the only truth in software development. Here I want to show, how coding to DIP principle can be harmful and that coding to concrete classes is not always a bad thing.
Consider the fallowing problem: we want to implement a solution that would create a distance matrix from one point to another. If direct line distance is greater than 50 kilometers, we would like to use A* algorithm and Dijkstra algorithm otherwise.

For that, lets create two classes: AStar and Dijkstra that inherit from common interface ShortestPath.

class ShortestPath
{
public:
  virtual int findPath(int startNodeID, int endNodeID) const = 0;
};

class Dijkstra : public ShortestPath
{
public:
  int findPath(int startNodeID, int endNodeID) const;
};

class AStar : public ShortestPath
{
public:
  int findPath(int startNodeID, int endNodeID) const;
};

After we have this, to avoid using concrete implementation in clients, we might want to create some kind of Factory Design Pattern for ShortestPath to avoid using a concrete classes. Lets do it in the simplest possible way:

class ShortestPathFactory
{
public:
  //returns Dijkstra if directDistance < 50 and AStar otherwise.
  std::unique_ptr<ShortestPath> create(int directDistance) const;
};

Now we can use it as such:

ShortestPathFactory algorithmFactory;
for (std::vector<int>::const_iterator it = myNodes.begin(); it != myNodes.end(); it++)
{
  for (std::vector<int>::const_iterator jt = myNodes.begin(); jt != myNodes.end(); jt++)
  {
    int directLineDistance = getDirectLineDistance(*it, *jt);
    std::unique_ptr<ShortestPath> algorithm = algorithmFactory.create(directLineDistance); 
    int realDistance = algorithm->findPath(*it, *jt);
  }
}

Looks great! But one thing bothers me here — creating and releasing resources gives an overhead in performance. Instead of using Factory, we could implement it as this:

class ShortestPathExecutor
{
private:
  AStar aStarAlgorithm;
  Dijkstra dijkstraAlgorithm;
public:
  int execute(int directDistance, int startNodeID, int endNodeID) const
  {
    if (directDistance < 50)
      return dijkstraAlgorithm.findPath(startNodeID, endNodeID);
    else
      return aStarAlgorithm.findPath(startNodeID, endNodeID);
  };
};

and use it:

ShortestPathExecutor algorithmExecutor;
for (std::vector<int>::const_iterator it = myNodes.begin(); it != myNodes.end(); it++)
{
  for (std::vector<int>::const_iterator jt = myNodes.begin(); jt != myNodes.end(); jt++)
  {
    int directLineDistance = getDirectLineDistance(*it, *jt);
    int realDistance = algorithmExecutor.execute(directLineDistance, *it, *jt);
  }
}

Here, now we don’t have overhead in performance, whenever we need to calculate the distance, we use Executor and expanding it is as easy as expanding Factory.
Though I am for sure not saying that Creation Patters are useless, but I do think they are abused. I remember first time when I used this approach to solve the problem, I’ve almost felt guilty for doing it “wrong”, not how I read somewhere in the books.

No comments yet

Leave a comment