c++oopdependency-injectionlaw-of-demeter

c++ Dependency Injection + Law of Demeter + logger/assert


I've seen two great videos (this and this) about dependency injection, law of demeter and global states (Singletons are considered as global).

I think I got the basic idea but I already have some singleton classes in my library. However if I want a testable and "well-designed" or "less coupled" code, I should use the DI and the LoD. This of course means that Singletons (as a design pattern) are evil because the caller does not now the implementation and any dependency on a global thing is bad, at least from a testing perspective.

More specifically I'm building a simple game engine without using any bigger 3rd party libs. This means I have to work with platform-specific and low level code as well.

Let's be more concrete. I have a Math section in my library where I have a class Vector2. It should be able to "throw assert" when invalid data is entered for one of its function. Or should be able to log it as an error. Or both. Until this time I've simply used a Singleton<Logger> so I was able to access it everywhere.

But I agree, these things should not be used and DI solves these issues. Eg. what if the logger is not initialized yet? What if I want a dummy logger for tests? And so on... What do you recommend for these cases (like the Logger and Assert classes)?

Also the LoD says that I should not use accessors for objects (like getObjectA()->getObjectB()->doSomething()). Instead, pass them as a parameter to the function/constructor. It's okay that it makes everything easier to test (and debug), but it can be painful to skip these functions.

Consider an example from the Unity engine. A GameObject has a method for getting a component from that object. Eg. if I want to manually transform my object I have no choice to call the "object getter", something like this:

this.GetComponent<Transform>().SetPosition(...);

This is against the LoD, isn't it?


Solution

  • This means I have to work with platform-specific and low level code as well.

    Use dependency inversion (not only injection).

    What do you recommend for these cases (like the Logger and Assert classes)?

    DI requires that you alter your APIs to allow you to inject things, where they are used. To avoid a situation where you have to add lots of extra parameters (one for logger, one for assert implementation, or global configuration settings and so on), group them together:

    Also the LoD says that I should not use accessors for objects (like getObjectA()->getObjectB()->doSomething()). Instead, pass them as a parameter to the function/constructor.

    There are a few issues with this type of call chaining:

    this.GetComponent<Transform>().SetPosition(...);

    This is against the LoD, isn't it?

    Yes. The code can be split as follows:

    void SetPosition(Transform& t) { t.SetPosition(); }
    

    client code:

    SetPosition(this.GetComponent<Transform>());
    

    This way, to set positions in client code, you no longer care about the interface of Transform. You also do not care in the implementation of void SetPosition(Transform& t), that there is an API called GetComponent.

    Alternative LoD implementation for your example above:

    void YourObject::SetTransformPositions()
    {
        GetComponent<Transformation>.SetPosition();
    }
    

    ... where the type of this is YourObject*.