Dependency Injection Example: C++ Server Logging
Example
class DI_Server { std::ostream* log; public: DI_Server(): log(0) {} DI_Server( std::ostream& log ): log(&log) {} // constructor injector void log_to( std::ostream& new_log ) { log = &new_log; } // setter injector void dont_log() { log = 0; } void on_connect( socket& connection ) const { if ( log ) *log << "Client connected from " << connection.ip() << std::endl; } void on_disconnect( socket& connection ) const { if ( log ) *log << "Client disconnected from " << connection.ip() << std::endl; } ... }; int main() { // We can easily share logs between servers: std::ofstream shared_log( "shared_log.txt" ); DI_Server a(shared_log), b(shared_log); // We can easily use seperate logs: DI_Server c,d; std::ofstream c_log( "c.txt" ) , d_log( "d.txt" ); c.log_to( c_log ); d.log_to( d_log ); // We have no problems using new log types: std::ostringstream e_log; DI_Server e(e_log); // We can take advantage of this for easy unit testing: std::string e_results = e_log.str(); assert( e_results == "No IP address given, disconnecting" ); }
Problematic Alternatives
1) Globals
As per usual, the problem with Globals is the inflexibility of the code that uses them:
class Server { ... void on_connect( socket& connection ) { std::cout << "Client connected from " << connection.ip() << std::endl; } void on_disconnect( socket& connection ) { std::cout << "Client disconnected from " << connection.ip() << std::endl; } };
The problem with code like this is that although we have a way of redirecting the logs via std::ofstream::rdbuf, such changes will affect everything that logs to std::cout, and it is impossible to have two servers log to separate sources:
int main() { Server a,b; // What can we do here? }
2) Server Management of the logs
This is a violation of SRP, which causes additional problems beyond the norm here:
class Server { std::ofstream log_file; std::ostream& log() { // PROBLEM: If we want to handle additional log types, we must handle them here // This could get really messy if we have a lot of types. We also can't easily share // resources -- we can't have to Servers log to the same file, as they'll both try to // open a separate write stream at the same time. Instead of just passing a single // log to multiple servers, we're forced to write a management framework to do this // for us. Or refactor to just use Dependency Injection. if ( log_file.is_open() ) return log_file; return std::cout; } public: void on_connect( socket& connection ) { log() << "Client connected from " << connection.ip() << std::endl; } void on_disconnect( socket& connection ) { log() << "Client disconnected from " << connection.ip() << std::endl; } ... };
page revision: 8, last edited: 04 Nov 2008 00:22