依赖注入(DI)是一种设计模式,允许程序在其组件之间具有松散的耦合。它用于实现控制反转(IOC),这是一个原则,指出程序不应负责创建或管理自己的依赖性,而应将其提供给它。
依赖注入有用的原因有几个:
- 可维护性:当组件松散耦合时,进行更改时它们的可能性较小。这使代码更加可维护和易于更改或扩展。
- 可检验性:依赖注入可以更轻松地测试,因为可以通过测试双打模拟或替换依赖。这允许孤立地测试单个组件,这可能会导致更可靠和准确的测试结果。
- 灵活性:依赖注入使得可以在运行时更改依赖关系的实现,而无需修改使用它的代码。这对于为不同环境或用例提供不同的依赖性实现很有用。
- 可重复使用性:与其依赖关系分离的组件可以在不同的情况下和不同的依赖性中重复使用。这可以帮助减少代码重复并增加整体代码重复使用。
- 解耦:依赖注入允许在应用程序的不同部分之间解耦。这使得让不同的团队在应用程序的不同部分工作成为可能,而不必担心其他部分的工作方式。
在GO中,可以通过多种技术来实现依赖注入,包括使用接口,工厂功能和结构嵌入。
在GO中实现DI的一种常见方法是通过使用接口。接口定义了结构必须实现的一组方法,但未指定应如何实现这些方法。这允许不同的结构用作相同接口的实现,可以将其传递到函数或struct作为依赖项。
。例如,让我们说我们有一个接口记录器和两个结构consolelogger和filelogger,它们都实现了记录器接口。我们可以创建一个将Logger接口作为依赖关系的结构应用程序,因此,当我们创建一个应用程序结构的变量时,我们可以传递任何实现Logger接口的struct。
type Logger interface {
Log(message string)
}
type ConsoleLogger struct {}
func (c *ConsoleLogger) Log(message string) {
fmt.Println(message)
}
type FileLogger struct {}
func (f *FileLogger) Log(message string) {
file, _ := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
defer file.Close()
file.WriteString(message)
}
type App struct {
logger Logger
}
func NewApp(logger Logger) *App {
return &App{logger: logger}
}
func main(){
cl := &ConsoleLogger{}
fl := &FileLogger{}
app1 := NewApp(cl)
app2 := NewApp(fl)
app1.logger.Log("Log message.")
//Log message.
app2.logger.Log("Log message.")
//Log message added to log.txt file
}
在上面的示例中,应用结构取决于Logger接口,但未指定应使用Logger的实现。相反,将实现作为参数传递给NewApp函数。这允许应用程序结构通过不同的记录实现重复使用,例如ConsoleLogger和Filelogger。
。可以通过使用诸如电线和Go-Micro之类的容器库来实现golang中DI的使用。这些库提供了一种自动解决和管理依赖性的方法,使编写和维护大型应用程序变得更加容易。
电线是用于GO的代码生成工具,可生成用于接线依赖关系的代码。它通过使用具有线结构标签的结构并生成必要的代码将这些结构连接在一起的结构来起作用。
这是如何使用电线进行依赖注入的一个例子:
// main.go
package main
import (
"fmt"
"github.com/google/wire"
)
// Define the interface
type Greeter interface {
Greet() string
}
// Define the struct that implements the interface
type EnglishGreeter struct{}
func (e *EnglishGreeter) Greet() string {
return "Hello"
}
// Define the struct that uses the interface
type App struct {
Greeter Greeter
}
// Define the provider set for wire
var AppSet = wire.NewSet(NewApp, NewEnglishGreeter)
// Provider function for App
func NewApp(greeter Greeter) *App {
return &App{greeter}
}
// Provider function for Greeter
func NewEnglishGreeter() Greeter {
return &EnglishGreeter{}
}
func main() {
app, err := AppSet.Build()
if err != nil {
panic(err)
}
fmt.Println(app.Greeter.Greet())
// Output: "Hello"
}
在上面的示例中,英语网格结构实现了迎接器接口,并且应用结构取决于问候器。我们定义了两个提供商功能:NewApp和NewEnglishGreeter,分别返回应用程序和英语网络结构。 Appset var是一组电线集,列出了要调用的提供商。然后在主函数中,通过调用提供商函数来构建应用程序,并将依赖项传递到正确的位置。
Go-Micro是微服务开发的更全面的框架,它基于Go-Micro库,该库可用于依赖型注入,配置管理,运输,编解码器,路由器,负载平衡,断路器等。
package main
import (
"fmt"
"github.com/micro/go-micro"
"github.com/micro/go-micro/server"
)
type Greeter struct {}
func (g *Greeter) Hello(ctx context.Context, req *proto.Request, rsp *proto.Response) error {
rsp.Greeting = "Hello " + req.Name
return nil
}
func main() {
service := micro.NewService(
micro.Name("greeter"),
micro.WrapHandler(AuthWrapper),
)
service.Init()
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
在这里,迎接器结构实施将处理请求的服务,并使用authwrapper包装,并使用proto.registergreetergreeterhandler函数在Micro.Server上注册。
go-micro还提供了一种通过使用Micro.Server的Init函数在整个服务中注册依赖项的方法,此函数采用了一个Micro.option,该功能允许将依赖关系传递给服务,例如:
:
type DBService struct{}
func (d *DBService) GetData() string {
return "Data from DB"
}
func main() {
db := &DBService{}
service := micro.NewService(
micro.Name("greeter"),
micro.WrapHandler(AuthWrapper),
micro.Option(server.Injector(func(container server.Container) error {
return container.Provide(db)
}))
)
service.Init()
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
这种方式可以由服务中的任何处理程序使用dbservice而无需手动传递。
Wire和Go-Micro都是用于管理依赖关系和提供依赖注入的强大库。电线更专注于生成用于接线依赖的代码,而Go-Micro是微服务开发的更全面的框架,并提供了其他功能,例如配置管理,运输,编解码器等。对于这些库,可以通过自动解决和接线依赖性的过程来提高大型GO应用程序的灵活性和可维护性。
GO中的依赖注入的一些局限性包括详细性和样板代码。 GO没有反射,因此解决依赖关系和将它们接线在一起可能比具有反射的语言更详细。同样,有许多不同的方法可以在GO中获得依赖注入,并且很难为特定用例选择合适的选择。
依赖注入并不总是针对每个问题的最佳解决方案,并且确实具有增加复杂性和冗长性的成本。权衡收益与费用并考虑特定用例的重要性是决定是否使用依赖注入。