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:
- 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.
- 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.
- 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 fromClassA
. Rather than writeClassB.ClassA.getNeededThing()
writeThis way
ClassC
doesn't even exist until it has what it needs to be useful. And it doesn't care if it wasClassA
orClassB
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
thatClassC
needs fromClassA
.Done this way
ClassC
doesn't even need to knowClassA
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).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.