Octopull/C++: Dependency Inversion
Home C++ articles Java articles Agile articles Bridge lessons

Dependency Inversion by Alan Griffiths

Purpose of Dependency Inversion

It is possible that a low level part of a system will need to access services that are only available at a higher level. For example data processing code might wish to provide progress information to the user interface.

It is clearly desirable that data processing components can be built and tested in isolation. That is, without being required to linking in the user interface or stub routines.

The term "dependency inversion" applies to these situations where a system is designed so that code may make use of services implemented in a "higher" layer. We examine three approaches that achieve this effect below.

Overview of the approaches

The three techniques we will examine all employ an abstract base class that defines the service used by the low level code. In our example this is an "Abstract Progress Display" defined as follows:




// Minimalist progress display interface
class MAbstractProgressDisplay
{
public:
    virtual void setEnd(unsigned int value) = 0;
    virtual void update(unsigned int current) = 0;
    virtual MAbstractProgressDisplay* makeClone() const = 0;
};


This interface is defined and used by the low level code. (The "makeClone" method is actually only used by the "Global prototype" technique described below.)

The techniques are:

Passing the implementation
The high level code is responsible for implementing the interface and passing the implementation to the low level code.
Passing a factory
An additional "factory" interface is defined by the low level code. This contains method for creating an implementation of the service (or more usually services) that are required.

The high level code is then responsible for implementing both the factory and the implementations of any services which the factory can construct.

Global prototype
Here the low level maintains a "prototype" instance of the service class. Any implementation of the required interface may be supplied as a prototype.

When the low level code requires the service a clone is made of the prototype.

Each of these techniques has advantages and these need to be understood when choosing between them.

Pros & Cons

The Passing the implementation is easy to understand. It is usually best where a single service is required. The costs are: the need to pass a reference to an implementation class and the need to provide a dummy implementation for in any low level test harnesses that invoke the code that requires it.

Passing a factory has greater costs as there are parallel heirarchies of factory and implementation classes to maintain. However, only a single reference to a factory need be passed to support multiple services.

Global prototype avoids the need to pass service or factory interfaces. However, there is only one prototype for the whole system (which may necessitate careful management). It also requires that a default implementation be available if the prototype has not been set explicitly.

Worked examples

Common code

The following code implementing the progress service is common between the examples below.


// MDummyProgressDisplay.h - Dummy progress display
class MDummyProgressDisplay : public MAbstractProgressDisplay
{
public:
    MDummyProgressDisplay();

    virtual void setEnd(unsigned int value);
    virtual void update(unsigned int current);
    virtual MAbstractProgressDisplay* makeClone() const;

private:
};






// MDottyProgressDisplay.h - Dotty progress display
class MDottyProgressDisplay : public MAbstractProgressDisplay
{
public:
    MDottyProgressDisplay();

    virtual void setEnd(unsigned int value);
    virtual void update(unsigned int current);
    virtual MAbstractProgressDisplay* makeClone() const;

private:
    unsigned int lastUpdate;
    unsigned int end;
};







// MDummyProgressDisplay.cpp - Dummy progress display
MDummyProgressDisplay::MDummyProgressDisplay() 
{
}


void MDummyProgressDisplay::setEnd(unsigned int value)
{
}

void MDummyProgressDisplay::update(unsigned int current)
{
}

MAbstractProgressDisplay* MDummyProgressDisplay::makeClone() const
{
    return new MDummyProgressDisplay(*this);
}





// MDottyProgressDisplay.cpp - Dotty progress display

const unsigned lineLength = 80;


MDottyProgressDisplay::MDottyProgressDisplay() :
lastUpdate(0),
end(lineLength)
{
}


void MDottyProgressDisplay::setEnd(unsigned int value)
{
    lastUpdate = 0;
    end = value;
}


void MDottyProgressDisplay::update(unsigned int current)
{
    unsigned temp = lineLength*current/end;

    for (int i = lastUpdate; i < temp; ++i)
    {
        std::cout << '.';
    }

    lastUpdate = temp;
}


MAbstractProgressDisplay* MDottyProgressDisplay::makeClone() const
{
    return new MDottyProgressDisplay(*this);
}


Passing the implementation

Header Implementation Client Code Overview of Idioms Pros & Cons

Header

      
void processPassingImplementation(MAbstractProgressDisplay&
display);
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons

Implementation

      
#include "MPassingImplementation.h"
#include "MAbstractProgressDisplay.h"

void processPassingImplementation(MAbstractProgressDisplay&
display)
{
    display.setEnd(100);

    for (unsigned i = 0; i < 100; ++i)
        display.update(i);
}
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons

Client code

      
    std::cout << "\nFirst solution - passing the
implementation from client code\n\n";
    std::cout << "\nPassing Dotty Implementation\n";
    {
        MDottyProgressDisplay dotty;
        processPassingImplementation(dotty);
    }

    std::cout << "\nPassing Dummy Implementation\n";
    {
        MDummyProgressDisplay dummy;
        processPassingImplementation(dummy);
    }
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons

Passing a factory

Header Implementation Client Code Overview of Idioms Pros & Cons

Header

      
    
// MLowLevelPassingFactory.h - passing a factory    
class MAbstractProgressDisplay;

class MAbstractProgressFactory
{
public:
    virtual MAbstractProgressDisplay* createProgressDisplay() = 0;
};

void processPassingFactory(MAbstractProgressFactory& factory);






// MHighLevelPassingFactory.h - passing a factory    
class MDottyProgressFactory : public MAbstractProgressFactory
{
    virtual MAbstractProgressDisplay* createProgressDisplay();
};

class MDummyProgressFactory : public MAbstractProgressFactory
{
    virtual MAbstractProgressDisplay* createProgressDisplay();
};
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons

Implementation

      
    
// MLowLevelPassingFactory.cpp - passing a factory    
void processPassingFactory(MAbstractProgressFactory& factory)
{
    MAbstractProgressDisplay* display =
factory.createProgressDisplay();

    display->setEnd(100);

    for (unsigned i = 0; i < 100; ++i)
        display->update(i);

    delete display;
}






// MHighLevelPassingFactory.cpp - passing a factory    

MAbstractProgressDisplay*
MDummyProgressFactory::createProgressDisplay()
{
    return new MDummyProgressDisplay();
}

MAbstractProgressDisplay*
MDottyProgressFactory::createProgressDisplay()
{
    return new MDottyProgressDisplay();
}
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons

Client code

      
    std::cout << "\nSecond solution - passing a factory from
client code\n\n";

    std::cout << "\nPassing Dotty Factory\n";
    {
        MDottyProgressFactory dotty;
        processPassingFactory(dotty);
    }

    std::cout << "\nPassing Dummy Factory\n";
    {
        MDummyProgressFactory dummy;
        processPassingFactory(dummy);
    }
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons

Global prototype

Header Implementation Client Code Overview of Idioms Pros & Cons

Header

      
void processUsingGlobalPrototype();
void setGlobalPrototype(MAbstractProgressDisplay& prototype);
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons

Implementation

      
#include <memory>
namespace
{
    std::auto_ptr<MAbstractProgressDisplay>&
getPrototype()
    {
        static std::auto_ptr<MAbstractProgressDisplay>
prototype
            (new MDummyProgressDisplay);

        return prototype;
    }
}


void processUsingGlobalPrototype()
{
    MAbstractProgressDisplay* display =
getPrototype()->makeClone();

    display->setEnd(100);

    for (unsigned i = 0; i < 100; ++i)
        display->update(i);

    delete display;
}


void setGlobalPrototype(MAbstractProgressDisplay& prototype)
{
    getPrototype() =
std::auto_ptr<MAbstractProgressDisplay>(prototype.makeClone());
}
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons

Client code

      
    std::cout << "\nThird solution - using a global
prototype\n\n";
    std::cout << "\nDefault [Dummy] Prototype\n";
    {
        processUsingGlobalPrototype();
    }

    setGlobalPrototype(MDottyProgressDisplay());
    std::cout << "\nUpdated [Dotty] Prototype\n";
    {
        processUsingGlobalPrototype();
    }
    
      
Header Implementation Client Code Overview of Idioms Pros & Cons