Design – How to pass data between objects when an IoC container is being used

designinversion-of-controlioc-containers

I'm working with a project that our architect has decided to use dependency injection for almost everything. We use an IoC container. One of the main issues that I keep coming across when using this pattern is "How do I pass this bit of data here to a different object that will be used later?"

Often, the easiest solution is to mark a specific class as being a "singleton" with our specific injector/container. This is bad for a few reasons. As we all know, singletons are evil and should never be used. Sometimes the class marked as "singleton", should actually have multiple instances when used in different parts of the code. Marking a class as a "singleton" creates a new dependency that we use an injector/container that supports the concept of marking a class as a "singleton".

Another solution is the modify how the injector/container creates the object by writing closures, or other specific logic when the injector is instantiating the object based on what is client is using it and the state of the clients. This is bad because it creates a new dependency of this functionality existing in the injector/container. It also violates the separation of concerns because it now makes the inject/container concerned with several different classes.

Here is an example use case of this problem:

  1. I am using ClassA early in the application from ClassB. I have some state/data I want to store in ClassA and use it later.
  2. Some more stuff happens in the application. The object of ClassA is either still on the stack farther down, or that part of the stack no longer exists and maybe there are no references to the object of ClassA. The method of classB may or may not still be on the call stack.
  3. Later in the application, I want to refer to the state/data of ClassA from ClassC.

Update – I'm adding an example

class ClassA {
private:
    shared_ptr<ClassState> state;
    shared_ptr<ClassB> b;
public:
    ClassA(shared_ptr<ClassState> state, shared_ptr<ClassB> b) {
        this->state = state;
        this->b = b;
    }
    void run() {
        // do stuff
        if (true /* blah blah blah */) {
            state->setState(true);
        } else {
            state->setState(false);
        }
        // do more stuff
        b->run();
    }
};

class ClassB {
private:
    shared_ptr<ClassC> c;
public:
    ClassB(shared_ptr<ClassC> c) {
        this->c = c;
    }
    void run() {
        // do stuff
        c->run();
        // do more stuff
    }
};


class ClassC {
private:
    shared_ptr<ClassState> state;
public:
    ClassC(shared_ptr<ClassState> state) {
        this->state = state;
    }
    void run() {
        //This is where I want to use that data that was stored earlier
        // in the application
        if (state->getState()) {
            // do stuff
        }
    }
};

void main() {
    // Create whatever magicaly IOC dependency injector
    shared_ptr<ClassA> a = injector.create<ClassA> ();
    a->run();
}

Best Answer

It sounds like you're wanting to just reach out and grab things as soon you need them. That's typical of procedural programming. No language, framework, pattern, or tool can stop you if you insist on working this way.

However, there is another way. Tell, don't ask says not to reach out for what you need (as is typically done through getters and static references) but to just let what ever you need be handed to you. That way you don't have to know how to find it. It finds you.

So you have ClassC that needs something from ClassA. Rather than write ClassB.ClassA.getNeededThing() write

class ClassC {
    String neededThing;
    public ClassC(String neededThing) { 
        this.neededThing = neededThing;
    }         
} 

This way ClassC doesn't even exist until it has what it needs to be useful. And it doesn't care if it was ClassA or ClassB that figured out how to get it what it needs.

Now that's just object creation. After objects are created they can still talk to each other if they have references to each other. Say there was anotherNeededThing that ClassC needs from ClassA.

class ClassA {
    ClassC c;
    String someOtherNeededThing;
    public ClassA(ClassC c, String someOtherNeededThing){ 
        this.c = c;
        this.someOtherNeededThing = someOtherNeededThing;
    }
    public void timeToGiveTheThing() {
        c.doYourThingWith(someOtherNeededThing);
    }      
} 

Done this way ClassC doesn't even need to know ClassA exists.

You might think you need a fancy DI container to make all this work but it can be done in main (if you have one, if not find your "composition root" and do it there).

int main() {
    ClassC c = new ClassC("Hello ");
    ClassA a = new ClassA("world", c);
    a.timeToGiveTheThing();
}

But you're stuck with a DI container you say? Fine, just list C as one of A's dependencies. So long as you get a reference to it you can talk to it. So long as whatever knows when this should happen has a reference to you then you will talk to it when you should. No need to ask.

If you come from procedural programming This will feel very different from your usual way of programming. It is a different way. We call this style object oriented programming. If you thought that you were doing that before because you were using an OOP language I'm sorry, that doesn't guarantee you're using OOP.

Once you get your head around this you'll likely find yourself creating constructors and method signatures with tons of arguments. That's when it's time to learn about primitive obsession and parameter objects. Just don't confuse parameter objects with encapsulated objects.