- The curse of non-const global variables
- Dependency injection as a cure
- Making good interfaces
- Related rules
Dependency injection as a cure
When an object uses a singleton, it injects a hidden dependency into the object. Thanks to dependency injection, this dependency is part of the interface, and the service is injected from the outside. Consequently, there is no dependency between the client and the injected service. Typical ways to inject dependencies are constructors, setter members, or template parameters.
The following program shows how you can replace a logger using dependency injection.
// dependencyInjection.cpp #include <chrono> #include <iostream> #include <memory> class Logger { public: virtual void write(const std::string&) = 0; virtual ~Logger() = default; }; class SimpleLogger: public Logger { void write(const std::string& mess) override { std::cout << mess << std::endl; } }; class TimeLogger: public Logger { using MySecondTick = std::chrono::duration<long double>; long double timeSinceEpoch() { auto timeNow = std::chrono::system_clock::now(); auto duration = timeNow.time_since_epoch(); MySecondTick sec(duration); return sec.count(); } void write(const std::string& mess) override { std::cout << std::fixed; std::cout << "Time since epoch: " << timeSinceEpoch() } }; class Client { public: Client(std::shared_ptr<Logger> log): logger(log) {} void doSomething() { logger->write("Message"); } void setLogger(std::shared_ptr<Logger> log) { logger = log; } private: std::shared_ptr<Logger> logger; }; int main() { std::cout << '\n'; Client cl(std::make_shared<SimpleLogger>()); // (1) cl.doSomething(); cl.setLogger(std::make_shared<TimeLogger>()); // (2) cl.doSomething(); cl.doSomething(); std::cout << '\n'; }
The client cl supports the constructor (1) and the member function setLogger (2) to inject the logger service. In contrast to the SimpleLogger, the TimeLogger includes the time since epoch in its message (see Figure 3.1).
Figure 3.1 Dependency injection