A while ago I had a conversation with a co-worker about Dependency Injection and Singleton patterns. The conversation was started from a posting on Reddit asking the alternatives to the Singleton design pattern and how they differ at their core. It's a fairly good conversation, but I saw a lot of confusion between the sub-patterns, if you will, of the Dependency Injection pattern.
There are some moving parts that we have to distinguish if we want to avoid using singletons. Dependency Injection is different from a Dependency Container and is also different from Dependency Inversion. They're all different patterns that contribute to good decoupled code, and I want to take a moment to discuss the difference between them.
Be careful not confuse dependency injection with a service container. Singletons violate numerous SOLID principles, and primarily they're violating Separation of Concerns. They're doing more than you asked. There's a difference between a single instance of an object that your application shares and a singleton. Singletons are typically objects that manage their own state. Managing the state of service objects is one of the most critical balancing acts of any application and should not be left up to the whim of every object from the moment it's created.
The Service Container is there to take on one part of this responsibility. It's got one dead-simple job: It's the place where your application keeps all of these objects. It's job is to store the objects and give them out when asked.
So great, now we've got all these service objects. But since we're separating the concerns out into all these different objects I've got a lot of them, and they all need one or the other to do something. I don't want to give them access to the container, because that would tightly couple all of them to the container and sort of defeat the purpose. That's where Dependency Injection comes in. Dependency Injection is also a dead-simple concept. We need to give these classes the things they need to work. It's the living embodiment of "tell, don't ask". Dependency Injection is just the practice of to wire all of your classes together with the things they need before we give them out.
For example, Class Foo needs class Bar to work, so before putting Foo in the container, we'll get a Bar object from the container and put it in Foo. Now we've got a constructed Foo object we can put in the Container. Now Foo didn't have to know the container existed, it just has the classes it needed and our Foo and Bar services are available when we need them. In addition we now have control over which objects share a single instance of a given service object and which ones don't. In reality most Containers will do this injection lazily, so not every object is constructed until you need it then it quick wires it together, but you get the idea. And in general it's best to keep as little as possible knowing about the container. There's this kind of captivating moment that happens when the first object is pulled out of the container and everything your application needs is assembled.
It's worth noting that the two aren't necessarily related. We can store objects in a Container without injecting dependencies into them, and we can Inject dependencies into a class without then putting them in a container.
Similarly, this practice shouldn't be confused with Dependency Inversion. Dependency Inversion is related to using interfaces in your classes. Now that we're injecting these objects into the classes instead of using them directly, we can start to rely on abstract functionality and less on the specifics. Allowing each piece of our code to be more flexible and interoperable. Let your service care less about the specifics and focus more on the results it needs the dependency for. That's what dependency Inversion is about. Dependency injection allows us to do Dependency Inversion.