What is dependency injection (DI)
To understand what dependency injection is, we first need to know what inversion of control is
Inversion of control
Inversion of control is a design principle or design pattern in object-oriented programming , Can be used to reduce the coupling between code , For example, the following code , class A The type of an attribute in is class B, If you don't use inversion of control mode ,class You need to create your own class B Example , This leads to coupling . To decouple , There's a pattern of inversion of control , About to reverse the acquisition of dependence , The dependency is no longer created by the user , It's injected by an external system .
class A {
Entity B;
}
What is dependency injection
Dependency injection is a standard technology , Used to generate flexible and loosely coupled code by flexibly providing components with all the dependencies they need to work
Dependency injection is an implementation of inversion of control , Other implementations are dependency lookup , The difference between them
- Dependency injection is to inject the dependency of an object in some way when the object is created
- Dependency lookup is when needed , The calling object can obtain the required dependency through the method provided by the framework of .
Implementation of dependency injection
The main ways of dependency injection are 3 Kind of :
- Constructor Injection
- setter Inject
- Interface injection
At present, the industry puts Dependency lookup
Some of the ways to implement are as follows Dependency injection
, for example Service locator
, So the boundaries between them are not very clear
wire brief introduction
wire Is a dependency injection tool , It takes the form of constructor Injection , But on the line, it's not done by reflection . In this article blog in ,go The team explained why it wasn't implemented in a reflective way . The main reason is that the implementation of reflection will bring errors to the runtime and is difficult to understand and debug , and go The team would prefer to find this problem at compile time .
example
In the example code, we will have a speaker
And a message
, stay speaker Initialization requires a message
, And then call speaker
Of Say
Method to print out message
Words of
No dependency injection
You can see , In this release ,speaker
You need to build it yourself message
, Obviously , This is a coupling point ,speaker
The implementation of is coupled with message
The implementation of the , Next , Let's transform the code , Turn it into a pattern of constructor Injection .
package main
import "fmt"
type Message string
func NewMessage(text string) Message {
return Message(text)
}
type Speaker struct {
Message Message
}
func (s Speaker) Say() {
fmt.Println(s.Message)
}
func NewSpeaker(text string) Speaker {
m := NewMessage(text)
return Speaker{
Message: m,
}
}
func main() {
s := NewSpeaker("hello, I am a speaker")
s.Say()
}
Constructor Injection
You can see , The difference between this version and the version without dependency injection is just message An example of is no longer by speaker
initialization , It's about being injected from the outside , The advantage of this is that we can have multiple NewMessage
The implementation of the , Which implementation and speaker
The implementation of is decoupled .
This technology works well on a small scale , But larger applications can have complex dependency diagrams , As a result, a lot of initialization code depends on the order , It is generally difficult to decompose code clearly , Especially because some dependencies are used many times . Replacing one implementation of a service with another can be painful , Because it involves adding a new set of dependencies , And delete the unused old dependencies to modify the dependency graph .
package main
import "fmt"
type Message string
func NewMessage(text string) Message {
return Message(text)
}
type Speaker struct {
Message Message
}
func (s Speaker) Say() {
fmt.Println(s.Message)
}
func NewSpeaker(m Message) Speaker {
return Speaker{
Message: m,
}
}
func main() {
m := NewMessage("hello, I am a speaker")
s := NewSpeaker(m)
s.Say()
}
wire edition
wire Designed to simplify the management of initialization code . Services and their dependencies can be described by code or configuration , then Wire Process the generated graph to figure out the order and how to deliver each service to what it needs . Change application dependencies by changing the function signature or adding or removing initializers , And then let Wire Do the tedious work of generating initialization code for the entire dependency graph .
Let's first add a wire.go
Note the construction constraints at the top , It's used to tell go You don't need to include this file at compile time
//+build wireinject
// The build tag makes sure the stub is not built in the final build.
package main
import "github.com/google/wire"
func InitializeSpeaker(text string) Speaker {
wire.Build(NewSpeaker, NewMessage)
return Speaker{}
}
And then let's transform main.go
You can see ,speaker
No longer in use NewSpeaker
Function creation , But use InitializeSpeaker
package main
import "fmt"
type Message string
func NewMessage(text string) Message {
return Message(text)
}
type Speaker struct {
Message Message
}
func (s Speaker) Say() {
fmt.Println(s.Message)
}
func NewSpeaker(m Message) Speaker {
return Speaker{
Message: m,
}
}
func main() {
s := InitializeEvent("hello, I am a speaker")
s.Say()
}
that ,InitializeSpeaker
This is how it works ? We can execute under the project wire
command ,wire
It will be automatically generated for us wire_gen.go
file , You can see , We don't need to write our own implementations ,wire Will automatically identify dependencies and generate code for us
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
// Injectors from wire.go:
func InitializeSpeaker(text string) Speaker {
message := NewMessage(text)
speaker := NewSpeaker(message)
return speaker
}
Last
There is no mention of wire Some of the advanced features and concepts of , for example injector、provider、 Interface binding, etc , Interested students can check wire Of file understand