Decoupling code for a static library and unit testing

Lately I have been working with some legacy code that makes the IRS tax code seem simple. My job was to separate some functionality from the main app into a static library and then unit test it. To make matters worse, the code that I was supposed to refactor depended on a class which was so tightly coupled to the rest of the product, that isolation was impossible. After much crying, pain, and rocking back and forth while clutching my knees, I finally found a solution.

So lets say you have a class defined in your main project called Convoluted that looks something like this:

//Header File Convoluted.h
#include <string>
class Convoluted2;

class Convoluted
{
public:
  std::wstring getData();
  Convoluted2 * getConvoluted2();
};

And, for simplicity, your all your library does is take a Convoluted pointer and prints out its data to the console.

void printConvoluted(Convoluted * convoluted)
{
  cout << convoluted->getData();
  cout << convoluted.getConvoluted2()->getData();
}

Easy enough right? Now lets say that Convoluted.cpp contains about 60 different #includes all of which #include their #includes and so on. A logistical nightmare! At this point you may think that the only way you are going to be able to include Convoluted.cpp in the library is to include every file in the main project, which frankly defeats the purpose of a library.

At this point, we should probably put down the office chair and apologize for the mean things we said to our coworker and step back for a moment. Why are we trying to include Convoluted.cpp? Well, its because it contains all of the definitions for the functions in the Convoluted.h. But what if we could tell the linker not to worry about those definitions until we actually call them at runtime? Thankfully we have pure virtual interfaces.

Lets create a class ConvolutedInterface that will allow us to call into Convoluted. This interface as a rule can only contain pure virtual functions.

class Convoluted2Interface;

class ConvolutedInterface
{
public:
  virtual std::wstring getData() =0;
  virtual Convoluted2Interface * getConvoluted2() =0;
};

We can now share this interface between the main and library projects. With pure virtual functions, the “=0” is the definition for the function. We don’t need a cpp file to link!

Wait? Why is Convoluted2 now Convoluted2Interface? Every class that you include in your interface must also be an interface, otherwise the linker would complain.

Next, we change our library to accept a ConvolutedInterface instead of a Convoluted

void printConvoluted(ConvolutedInterface * convoluted)
{
  cout << convoluted->getData();
  cout << convoluted.getConvoluted2()->getData();
}

And voila, we can pass a Convoluted object like so

//Main app side
void someFunction()
{
   ConvolutedInterface * conv = new Convoluted();
   printConvoluted(conv);
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Solve this equation to verify you are a not a computer * Time limit is exhausted. Please reload the CAPTCHA.