class Logger { public: void log(std::string s) { /* implementation here */ } } class Foo { private: Logger *logger; public: Foo() { logger = new Logger(); } void doSomethig() { /* does something */ this->logger->log("done"); } }There is one big problem with this implementation: class Foo is strongly tied to the specific instance of the class Logger, which has the following implications:
1) If you ever need to write a new logger and make your application use it, you will have to browse through all the classes depending on the logger and refactor them.
2) There is no way to write good unit tests for class Foo, since it requires the environment setup expected by Logger (like syslog, database, log directory on the filesystem, etc.).
A solution to this problem is to turn Logger into an abstract interface and inject its specific implementation (hence term "dependency injection") into class Foo through a constructor. Because C++ implements multiple inheritance instead of interfaces, let's just use an abstract class ILogger:
class ILogger() { virtual void log(std::string s) = 0; }; class Logger : public ILogger() { void log(std::string s) { /* implementation goes here */ } }; class Foo { private: ILogger *logger; public: Foo(ILogger *l) { logger = l; } void doSomethig() { /* does something */ this->logger->log("done"); } }With this implementation it is now possible to create different loggers inheriting from ILogger and use them with class Foo without any modification of the Foo itself (it follows so called open/closed principle, which means that you can modify object's behaviour without changing its source code). It is also possible to write unit tests to cover doSomething(), using a fake logger having an empty log() method, or even doing some assertions inside. The classes are loosely coupled, which means that you can use them like independent building blocks, and make changes to the application architecture much more easily.
However, if you have a lot of classes, it is tedious to instantiate all of them with proper dependencies, like this:
Foo *foo1 = new Foo(new FileLogger()); ... Foo *foo2 = new Foo(new FileLogger()); ... Foo *foo3 = new Foo(new FileLogger());A common pattern that solves this problem is called a dependency injection container. It's a mechanism that helps you managing application through registering and resolving dependencies. More advanced containers (like Google Guice for Java or Symfony Framework Container for PHP) provide additional services, like support for injecting dependencies through setters or defining dependencies in separate configuration files (XML or YAML for example). However, these features rely heavily on reflection and cannot be easily used in C++, so let's just create a simple dependency resolver:
class Resolver { public: static std::map<std::string, void *> registeredTypes; template<class T> static void Register(const T*); template<class T> static T* Resolve(); }; std::map<std::string, void *> Resolver::registeredTypes; template<class T> void Resolver::Register(const T* object) { registeredTypes[typeid(T).name()] = (void *)object; } template<class T> T* Resolver::Resolve() { return (T *)registeredTypes[typeid(T).name()]; }The resolver implements two methods: Register() and Resolve(), which store and retrieve data from a map holding associations between type name and a pointer to a specific object of that type. You can now use it to simplify dependency management of the initial Foo class:
/* Bootstrap code */ Resolver::Register<ILogger>(new FileLogger()); /* Application code */ Foo *foo1 = new Foo(Resolver::Resolve<ILogger>()); ... Foo *foo2 = new Foo(Resolver::Resolve<ILogger>()); ... Foo *foo3 = new Foo(Resolver::Resolve<ILogger>());The resolver can also be used as a service locator, which means that a class uses a resolver instance to retrieve the dependencies itself, instead of relying on injection through a constructor or setter:
class Foo { private: ILogger *logger; public: Foo() { logger = Resolver::Resolve<ILogger>(); } void doSomethig() { /* does something */ this->logger->log("done"); } } /* Bootstrap code */ Resolver::Register<ILogger>(new FileLogger()); /* Application code */ Foo *foo1 = new Foo(); ... Foo *foo2 = new Foo(); ... Foo *foo3 = new Foo();Using a resolver or a full-blown dependency container to inject class dependencies is generally considered better practice than using service locators within classes, because it forces you to initialize dependencies before injecting them (with service locator you may forget to register all dependencies required by a class before trying to make an object).
The resolver is publicly available for download on Github. It's super simple (around 30 lines of code, not including unit tests and comments) and uses a simplified BSD license, which means that you can use and distribute it with any project (free or commercial) without any restrictions, provided that you keep the original copyright notice.
2 comments:
This pattern is not dependency injection, but a service locator pattern. Dependency injection is colloquially implemented by accepting all parameters in a type's constructor, so something like this:
struct Thingy {
Thingy(Logger*);
};
Most dependency injection frameworks also come with an inversion of control container which *automatically* constructs objects for you.
I've written such a framework, although it is not (imo) generally consumable because it was written to suit my own needs. You can see the framework here: http://bitbucket.org/cheez/dicpp
You are right, thanks for pointing that out. I fixed the post and changed the project name from "container" to "resolver", which should be less confusing.
Post a Comment