In parts 1 and 2 of this series on dependency injection I showed how dependency injection is implemented, and why it is a powerful technique. The motivating examples and coding solutions centered around data provided as parameters in some way, often parameters that are an indirection, for example a file name. This part of the series discusses another common way of making data available to functions: global variables.
There are countless articles available online that explain why global variables are bad, and how to deal with them. I’m not going to repeat the details here. Though, one canonical way to remove a dependency on a global variable is to make the data a parameter of the function and have the controller inject the value.
package di
import (
"flag"
"fmt"
)
var (
// In Golang, this is the closest we come to a global variable.
SomeFlag = flag.String("string_flag", "", "A flag")
)
func notLikeThis() {
fmt.Printf("I'm using the global flag directly. Here it is: %s", *SomeFlag)
}
func butLikeThis(msg string) {
fmt.Printf("I'm data properly. Here it is: %s", msg)
}
func controller() {
notLikeThis()
butLikeThis(*SomeFlag)
}
The new function butLikeThis()
now enjoys the same benefits as the examples in the previous articles: it’s easier to test and configure, and much easier to understand and refactor, because the hard dependency on the global variable is gone.
Two important things to note:
- Singletons [1], i.e. instances of the Singleton anti-pattern, are global variables and should be treated as such.
- When writing new code, avoid creating global variables whenever possible. This subject is subtle enough to merit detailed exploration at a later time.
Data sharing – The elephant in the room
Whether we pass parameters to functions, access global variables or look up singletons, we are always sharing data: parameter values are copied or passed by reference from the caller, globals and singletons are directly accessed data. Functions consume and possibly modify this data without knowledge which other part of the system may have access to the same instance of that data.
Sharing mutable objects, in particular in concurrent code, is a source of subtle and often surprising errors [PP]. So it’s worth taking a closer look how introducing dependency injection over the other patterns discussed here, affects sharing.
Let’s get the easy case out of the way first. Replacing explicit access to globals in functions with parameters where (potentially) the controller passes the value of a global as argument doesn’t change the degree of sharing. It’s the same as before. The refactoring suggested above is purely an improvement of the using function.
At a closer look, the examples in the previous articles also do not change sharing properties of the underlying data. In those cases, the “address” of the data is passed as argument, but the data is then read from files. Nothing is stopping other code from modifying those same files while you are reading them. Or more likely, such attempts would lead to errors somewhere. So in healthy cases, where dependency injection is used to improve code structure, the nature of data sharing does not change, unless you take extra steps.
One thing you will encounter when you implement the suggestions in this article series is that the controller will instantiate wrapper objects around data. These objects are easy to share across different parts of your program. This can be a blessing or a curse. The code structure I advocate here makes it more explicit where sharing may occur. Like many other programming techniques, it requires judgment to decide when to share and when to copy the data once you have it available for injection. This is where you come in.
Required Reading
- Gamma, Erich, Richard Helm, Ralph Johnson, John Vlissides, and Grady Booch. 1994. Design Patterns: Elements of Reusable Object-Oriented Software. 1st edition. Reading, Mass: Addison-Wesley Professional.
- Thomas, David, and Andrew Hunt. 2019. The Pragmatic Programmer: Your Journey To Mastery, 20th Anniversary Edition. 2nd edition. Boston: Addison-Wesley Professional.