Dependency Injection – Differences from Simple Interface Usage

cdependency-injectiondesign-patterns

While trying to solve an issue, explained on the StackOverflow forum, somebody advised me to use dependency injection. For personal reasons, the moment a person mentions to me the usage of a design pattern, I always start thinking of very difficult constructions. After some thinking and investigating, I've invented following construction on my own (pseudo-code):

General header file

interface ILogger {
public:
  void writeMsg(std::string);
};

class Application_Logger : ILogger {
public:
  void writeMsg(std::string output) {
    std::printf(stringutils::format("Application : %s", output);
  }
};

class Test_Logger : ILogger {
public:
  void writeMsg(std::string output) {
    std::printf(stringutils::format("Test : %s", output);
  }
};

Application.h header file:

ILogger logger = nullptr;

Unit_test.h header file:

ILogger logger = nullptr;

Application_startup.cpp:

if (logger == nullptr)
  ILogger logger = Application_Logger();

Unit_testing_startup.cpp:

if (logger == nullptr)
  ILogger logger = Test_Logger();

Common_used.cpp:

logger.writeMsg("<information>");
logger.writeMsg("<more information>");

Application output

Application : <information>
Application : <more information>

Unit test output

Test : <information>
Test : <more information>

I have no idea whether or not this works, but I believe it does (assuming that it is possible to launch a piece of code, enabling to fill in the interface pointer).
In my opinion, this is not a special construction, but the basic usage of interfaces, not worthy of being called a design pattern. Am I correct and in case not, what needs to be added/modified in order to make a direct injection pattern out of this?

After some first comments, I'm starting to understand why this is not a direct injection, so hereby another example, trying to make a very simple constructor direct injection (just being able to switch between two ILogger interfaces):

General header file

interface ILogger {
public:
  void writeMsg(std::string;int);
};

class Simple_Logger : ILogger {
public:
  void writeMsg(std::string output;int severity) {
    std::printf(stringutils::format("[%d] : %s", severity, output));
  }
};

class Detailed_Logger : ILogger {
public:
  void writeMsg(std::string output;int severity) {
    if (severity == 0) {
    std::printf(stringutils::format("Very important : %s", output));
    } else {
    std::printf(stringutils::format("[%d] : %s", severity, output));
    }
  }
};

Application.h header file:

ILogger logger = nullptr;

Application_startup.cpp (based on args):

if (logger == nullptr) {
  if args == "Simple"
  ILogger logger = Simple_Logger();
  else : ILogger logger = Detailed_Logger();
}

If this is correct, it means that DI means that your application's processing is based on interfaces, and that you let outer data (arguments, configuration file content, interactive input, …) decide which implementation is chosen for those interfaces. Am I right this time? (On the Wikipedia page, it's not clear where the mentioned service is coming from)

Best Answer

I agree with the other answers, but it appears there is still some confusion. So let's look at an example.

class Logger
{
public:
    virtual void writeMessage(std::string&) = 0;
}

class AppLogger : public Logger
{
public:
    virtual void writeMessage(std::string &str) { /* ... */ }
}

class TestLogger : public Logger
{
public:
    virtual void writeMessage(std::string &str) { /* ... */ }
}

class DataProcessor
{
public:
    DataProcessor(Logger &logger)   // dependency is injected via constructor
    {
        // The DataProcessor does NOT know anything about AppLogger or TestLogger.
        // In particular, the DataProcessor does NOT create any loggers!!
        // This is the key point of dependency injection - the dependencies are
        // provided from the outside of the class.

        this->logger = logger;      // save it for later
    }

    void ProcessSomeData()
    {   
        std::string msg("Processing some data");
        logger.writeMessage(msg);   // use the injected dependency
        // ...
    }

private:
    Logger logger;
}

void someFunctionSomewhere()
{
    AppLogger logger;
    DataProcessor processor(logger);    // inject the dependency here
    processor.ProcessSomeData();
}

It's important that DataProcessor does not create or rely on any specific Logger implementation, but just on the interface. This allows you to inject any concrete implementation of Logger during runtime.

Note that I've shown constructor injection. There are other ways to inject depdencies, but the general idea is that classes which the DataProcessor uses (i.e. its dependencies) are not created by the DataProcessor but injected into it from the outside.