Octopull/C++: Dependency Inversion |
---|
Home | C++ articles | Java articles | Agile articles | Bridge lessons |
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.
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:
The high level code is then responsible for implementing both the factory and the implementations of any services which the factory can construct.
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.
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.
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); }
Header Implementation Client Code Overview of Idioms Pros & Cons
Header
Header Implementation Client Code Overview of Idioms Pros & Consvoid processPassingImplementation(MAbstractProgressDisplay& display);
Implementation
Header Implementation Client Code Overview of Idioms Pros & Cons#include "MPassingImplementation.h" #include "MAbstractProgressDisplay.h" void processPassingImplementation(MAbstractProgressDisplay& display) { display.setEnd(100); for (unsigned i = 0; i < 100; ++i) display.update(i); }
Client code
Header Implementation Client Code Overview of Idioms Pros & Consstd::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
Header
// MLowLevelPassingFactory.h - passing a factory class MAbstractProgressDisplay; class MAbstractProgressFactory { public: virtual MAbstractProgressDisplay* createProgressDisplay() = 0; }; void processPassingFactory(MAbstractProgressFactory& factory);
Header Implementation Client Code Overview of Idioms Pros & Cons// MHighLevelPassingFactory.h - passing a factory class MDottyProgressFactory : public MAbstractProgressFactory { virtual MAbstractProgressDisplay* createProgressDisplay(); }; class MDummyProgressFactory : public MAbstractProgressFactory { virtual MAbstractProgressDisplay* createProgressDisplay(); };
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; }
Header Implementation Client Code Overview of Idioms Pros & Cons// MHighLevelPassingFactory.cpp - passing a factory MAbstractProgressDisplay* MDummyProgressFactory::createProgressDisplay() { return new MDummyProgressDisplay(); } MAbstractProgressDisplay* MDottyProgressFactory::createProgressDisplay() { return new MDottyProgressDisplay(); }
Client code
Header Implementation Client Code Overview of Idioms Pros & Consstd::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
Header
Header Implementation Client Code Overview of Idioms Pros & Consvoid processUsingGlobalPrototype(); void setGlobalPrototype(MAbstractProgressDisplay& prototype);
Implementation
Header Implementation Client Code Overview of Idioms Pros & Cons#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()); }
Client code
Header Implementation Client Code Overview of Idioms Pros & Consstd::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(); }