Dependency Injection
In an object oriented application each object you create should have well defined and narrow focused responsibilities. In other words, each of your objects should have just a single responsibility. If you have an object that performs calculations, database updates, sends emails, imports data and performs logging, then this is a good candidate for breaking down into separate objects each with more narrow focused responsibilities. However, once you have separated your objects in this way you still need a way of combining them so they can work together.
Car and Engine example
Let's consider a simple example where you are designing an application that represented cars with engines. We may have two objects as follows:
The applyPressureToAcceleratorPedal() function takes an amount of pressure argument which causes the speed of the car to change. The getCurrentSpeed() function returns the current speed.
The setFuelIntake() function opens the fuel valve to accept more or less fuel based on the amount specified. The higher the value, the higher the torque of the engine.
Let's make a first attempt at how our Car might be implemented
<component name="Car" output="false">
<cfset variables.engine = createObject("component","SimpleEngine").init()>
<cffunction name="init" output="false">
<cfreturn this>
</cffunction>
<cffunction name="applyPressureToAcceleratorPedal" output="false">
<cfargument name="amount" required="true">
<cfset variables.engine.setFuelIntake(arguments.amount)>
</cffunction>
<cffunction name="getCurrentSpeed" output="false">
<cfset var torque = variables.engine.getTorque()>
<cfset var speed = ... calculate speed based on torque ...>
<cfreturn speed>
</cffunction>
</cfcomponent>
So to use our car object we may write some code such as:
<cfset car = createObject("component","Car").init()>
<cfset car.applyPressureToAcceleratorPedal(5)>
<cfoutput>
The car's speed is #car.getCurrentSpeed()#
</cfoutput>
In this simple example we have only two objects that are required to work together; our car and our engine. The car on it's own does not know about the internal workings of an engine so it needs an engine object to perform "engine" type tasks. In other words the car object is dependant on the engine object and cannot function without it.
You will notice that when using this approach the Car needs to know how to create the Engine object. In this example the Engine object does not need any parameters passed to it's init() function, but if the Engine was changed so that it did need some initialisation parameters then we would also need to update our Car object accordingly.
Object coupling
In object oriented design the more one object needs to know about another object the more "tightly bound" they are together. This increases the likelihood that if one object needs to be changed (e.g. the Engine) then the other object will also need to be changed (e.g. the Car).
This measure of how tightly bound objects are to one another is called coupling.
High coupling between two objects means the objects know a lot about each other. For example one object may call a function on another object that in turn returns a struct. The first object would need to know the details of each of the struct members to be able to use the returned value.
Low coupling means objects need to know very little about each other. For example one object may simply ask another object to perform a task and there is no return value. The first object has a lower coupling to the second object compared with the previous example that returned a struct.
Low coupling leads to more maintainable systems because it reduces the likelihood of a change in one object having an effect on other objects.
Reducing our coupling
In our car example, the Car knows the details of how an Engine object is created. As mentioned above, this could lead to changes in the engine requiring changes to the car as well, which is a situation we would like to avoid.
Let's change our Car slightly and have the Engine provided to the Car in the init() function rather that having the Car create the Engine.
<component name="Car" output="false">
<cfset variables.engine = 0>
<cffunction name="init" output="false">
<cfargument name="engine" required="true">
<cfset variables.engine = arguments.engine>
<cfreturn this>
</cffunction>
<!--- Other code here --->
</cfcomponent>
So now our Engine object needs to be explicitly provided to our Car object. To create and use our car we would now have code such as:
<cfset engine = createObject("component","SimpleEngine").init()>
<cfset car = createObject("component","Car").init(engine)>
<cfset car.applyPressureToAcceleratorPedal(5)>
<cfoutput>
The car's speed is #car.getCurrentSpeed()#
</cfoutput>
Or a slight variation on this idea that uses a setEngine() function instead.
<component name="Car" output="false">
<cfset variables.engine = 0>
<cffunction name="init" output="false">
<cfreturn this>
</cffunction>
<cffunction name="setEngine" output="false">
<cfargument name="engine" required="true">
<cfset variables.engine = arguments.engine>
</cffunction>
<!--- Other code here --->
</cfcomponent>
And creating and using our car:
<cfset engine = createObject("component","SimpleEngine").init()>
<cfset car = createObject("component","Car")>
<cfset car.setEngine(engine)>
<cfset car.init()>
<cfset car.applyPressureToAcceleratorPedal(5)>
<cfoutput>
The car's speed is #car.getCurrentSpeed()#
</cfoutput>
In both of these examples the engine is being explicitly provided to the car object. In the first example the engine is provided through the init() function. In the second example it is provided through the setEngine() function.
When we pair up our car and engine in this way, we can say that the engine object is being injected into the car object.
From this we can see that the car is dependent on the engine object, and in these examples the engine is injected into the car. Code that is written in this way is considered dependency injection.
Object creation separated from object usage
Our car object has now become a little more work to create. Previously the car and engine was created with a single line of code, but now we need two or more lines of code; create the engine, create the car, then associate them together.
However at the same time this change has the more important impact of reducing the coupling of the car and the engine.
So if we now change the creation of the engine so that it requires some additional parameters on creation, it would have no impact on the car:
<!--- Engine now needs some additional parameters --->
<cfset engine = createObject("component","SimpleEngine").init(numberOfCylinders=6)>
<!--- But the car does not need to be changed and everything else works correctly as before. --->
<cfset car = createObject("component","Car").init(engine)>
<cfset car.applyPressureToAcceleratorPedal(5)>
<cfoutput>
The car's speed is #car.getCurrentSpeed()#
</cfoutput>
Changing the implementation of our dependency
The most important advantage of dependency injection is to separate the creation of objects from their usage, which in turn reduces the coupling between objects, however there is an additional benefit where different kinds of dependencies (e.g. the Engine) can be provided to the dependent object (e.g. the Car).
Suppose we created a new kind of engine called a NuclearEngine:
It is very important to note that the functions here are identical to the SimpleEngine we have used above. In this case the setFuelIntake() function adjusts the amount of nuclear plasma (or whatever stuff is in nuclear systems) released within the engine.
Let's look at how our nuclear engine would be used in code. In this case, our nuclear engine also has a dependency of a NuclearCore:
<cfset core = createObject("component","NuclearCore").init()>
<cfset engine = createObject("component","NuclearEngine").init(core)>
<cfset car = createObject("component","Car").init(engine)>
<cfset car.applyPressureToAcceleratorPedal(5)>
<cfoutput>
The car's speed is #car.getCurrentSpeed()#
</cfoutput>
So we made a significant change to the engine, but because we kept the set of engine functions unchanged (i.e. we kept the interface the same) then we can readily swap our new nuclear powered engine into our Car object.
Introducing an object factory
By separating the creation of our objects from the usage of our objects we have reduced coupling, improved maintainability, but increased the complexity of creating these objects. If you needed to create car objects in many places in your application it would become very messy very quickly due to the need for a core object, an engine object and a car object for each car with a nuclear engine. It is better to move this object construction into its own factory object.
This factory can hide any complexities associated with creating objects.
This may be implemented as follows:
<component name="CarFactory" output="false">
<cffunction name="init" output="false">
<cfreturn this>
</cffunction>
<cffunction name="createSimpleCar" output="false">
<cfset var engine = createObject("component","SimpleEngine").init()>
<cfset var car = createObject("component","Car").init(engine)>
<cfreturn car>
</cffunction>
<cffunction name="createNuclearPoweredCar" output="false">
<cfset var core = createObject("component","NuclearCore").init()>
<cfset var engine = createObject("component","NuclearEngine").init(core)>
<cfset var car = createObject("component","Car").init(engine)>
<cfreturn car>
</cffunction>
</cfcomponent>
Object factories are good candidates for being placed in the application scope:
<cfset application.carFactory = createObject("component","CarFactory").init()>
And to use our factory, we may write:
<cfset car = application.carFactory.createSimpleCar()>
<cfset car.applyPressureToAcceleratorPedal(5)>
<cfoutput>
<p>
The simple car's speed is #car.getCurrentSpeed()#
</p>
</cfoutput>
<cfset nuclearCar = application.carFactory.createNuclearPoweredCar()>
<cfset nuclearCar.applyPressureToAcceleratorPedal(5)>
<cfoutput>
<p>
The nuclear powered car's speed is #car.getCurrentSpeed()#
</p>
</cfoutput>
Terminology
Dependency Injection is the common term for this technique of associating objects, however this technique is also known as "Inversion of Control", or "IoC".
The idea behind this is that conventional control would see an object grabbing whatever things it needed, as applied in procedural programming. When this control is inverted your objects are instead given what they need and they are only permitted to use what they have been explicitly given.
Another term used to describe the joining of objects using dependency injection is wiring. In other words you may say that when one object is injected into another object then they are wired together.
Conclusion
One of the fundamental guides in good object oriented design is to have objects that have low coupling. Having objects that have single responsibilities and separating out object construction from object usage can significantly help in reducing the coupling between objects. The side effect of this is that object creation becomes more complicated, but an object factory provides a good technique to manage the creation of these objects.