2

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)

  • 2
    What you're doing is not dependency injection. Please see en.wikipedia.org/wiki/Dependency_injection#Examples for some examples. – doubleYou Feb 1 '18 at 9:29
  • @doubleYou: thanks, I already had a look at that wiki-page but I don't understand it: DI is about switching from one thing to another without needing to compile, but there is only one example, while I'm looking for an example of such a switch without re-compilation (if you don't switch by re-compiling, on what do you base your switch then?). – Dominique Feb 1 '18 at 10:08
1

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.

  • Meanwhile I've understood what direct injection is all about: it means that you let outside data (configuration files, commandline parameters, ...) decide which implementation is to be used. Thanks. – Dominique Feb 26 '18 at 10:23
9

Dependency injection is not about programming to interfaces rather than implementations. That is a worthy goal, but a different one.

No, DI is about being able to swap interface providers without recompiling the world. When ever your code contains a concrete constructor call such as Application_Logger(), you can no longer change away from using this particular class without recompiling. If you had obtained it in another way - from a Factory, through a registry, or via DI - then it would be possible to change a configuration file and exchange loggers. As it is, you'll have to recompile and redeploy your application to change such a minor configuration detail. If you manage large software systems, this is annoying and expensive.

(This is why DI and associated techniques become more important the larger and more complex your systems are. Unfortunately, examples that fit into a Stackexchange question are virtually never convincing arguments for using enterprise-level concepts.)

  • Thanks, I now understand why my example is wrong: as you mention, I need to recompile my application in order to switch from Application_Logger to Test_Logger, but then what do I need to do in order to get a system where I can switch without needing to recompile? – Dominique Feb 1 '18 at 9:55
  • @Dominique Make the constructor of your consumer (e.g. MyController) accept a Logger (interface) and then pass an Application_Logger or Test_Logger object as parameter to that constructor during runtime. (This would be constructor injection, see wikipedia for other options). – doubleYou Feb 1 '18 at 10:16
  • Compile-time configured DI libraries like Guice would disagree with this answer. – Sebastian Redl Feb 1 '18 at 10:30
  • @doubleYou: I adapted your question, based on your last comment, I believe I'm getting there, thanks. – Dominique Feb 1 '18 at 10:50
1

Dependency injection is just a small step over programming to interfaces. Specifically, it's about supplying objects with the dependencies they need (in the form of some interface implementation) externally (e.g. by passing a pointer to the constructor).

So use member variables instead of globals, and you've got DI.

You might not think this is worthy of being a design pattern, but the history of enterprise software development disagrees. This being a pattern is important to distinguish it from previous patterns such as Service Locator, which was dominant in the early JavaEE world. Service Locator is also programming to interfaces, but the method of obtaining an implementation of the interface differs. DI's main advantages over SL is that it is easier to set up tests, and that your classes are less tightly coupled to the framework you're using.

Or to put it another way: simple interface usage just means using interfaces. DI is about how you obtain the interface.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.