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;
    }
    ...
};