Introduction
Dependency Injection is simply said, the idea that your classes should depend on abstraction, i.e. the abstraction of a datasource for example, rather than on concrete implementations. This means that classes will depend on interfaces rather than on real classes.
This has several advantages:
- It is easy to exchange on implementation for another, without changing the changing the client code.
- Because an interface usually covers a small part of the total API of a class, you can precisely determine which class gets access to which methods and functionality. If for example you have a datasource class, you can have a read- and a write-interface, and possible a combination of the two. Each client can then have either interface, depending on the need. This is an example of the principle of least privilege.
It looks like this:
The summary of this pattern is: you should depend on interfaces, not on concrete implementations. This make it easier to swap different implementations of the same functionality. This is also the ‘D’ in SOLID.
Implementing this in Go.
Since dependency-injection is a very common pattern, there are libraries like Google Wire for Go. In a later article I will implement Dependency Injection using that, for now I will keep it small and simple.
Let us start with the usual preliminaries:
package main
import "fmt"
In this example I will build an imaginary carfactory which just needs wheels: big wheels and small wheels. Since the machines producing this wheels have the same interface, we can use that:
type WheelProducer interface {
ProduceWheel() string
}
For simplicity’s sake, our wheelproducer produces only a string representation of a wheel.
The CarFactory has an instance of a WheelProducer:
type CarFactory struct {
WheelProducer WheelProducer
}
Notice that we define the WheelProducer field in terms of the interface, not of a concrete class.
Now on to our first WheelProducer:
type SmallWheelProducer struct{}
func (swp SmallWheelProducer) ProduceWheel() string {
return "small wheel"
}
This code is self-explanatory. Note that SmallWheelProducer implements the WheelProducer interface.
Next we do the same thing for the BigWheelProducer:
type BigWheelProducer struct{}
func (bwp BigWheelProducer) ProduceWheel() string {
return "big wheel"
}
Time to test
Now we can test this thing:
func main() {
wheelproducer := BigWheelProducer{}
factory := CarFactory{
WheelProducer: wheelproducer,
}
wheel := factory.WheelProducer.ProduceWheel()
fmt.Println(wheel)
}
Line by line:
- We create a WheelProducer implementation, namely BigWheelProducer.
- Next we create a CarFactory and pass it the WheelProducer implementation
- After that is done, we produce a wheel, and print out the result.
Note that
- The CarFactory does not depend on a concrete implementation
- That the concrete WheelProducer classes may have more methods and functionality, however, the CarFactory cannot access these. One could say that this is an example of the Principle of Least Privilege
- Because we are using interfaces, the client, in our case the CarFactory cannot change the internal state of our objects.
Conclusion
Implementing this pattern is not very difficult. The main thing to keep in mind is to use interfaces instead of concrete implementations.
In a next post, I will implement Dependency Injection using Google’s Wire package, and see if that makes things any easier.