#作者:曹付江
文章目录
- 1、示例: 管理依赖关系的容器
- 1.1. 日志记录器设置
- 1.2. 数据库连接设置
- 1.3. 管理依赖关系的容器
- 2、如何使用容器
- 3、结论
依赖注入(DI)是一种在软件应用程序中促进松散耦合和可测试性的设计模式。它允许将依赖关系(如服务、配置或数据库)注入到组件中,而不是让组件直接创建或管理依赖关系。这将使代码更模块化、更易维护、更易测试。
在本篇文章中,我们将使用一个实用的 Golang 示例来探讨依赖注入模式。我们将分解代码,并解释如何在实际场景中实现依赖注入。
1、示例: 管理依赖关系的容器
该示例由三个 Go 文件组成,它们共同创建了一个 “容器”,用于管理日志记录器、数据库连接和配置等依赖项。让我们深入代码,看看 DI 是如何应用的。
1.1. 日志记录器设置
第一个文件使用 zap 日志库设置了一个日志记录器。日志记录器使用配置文件初始化,NewLogger 函数负责创建日志记录器实例。
func NewLogger(zapConfig string) (*zap.Logger, error) {
file, err := os.Open(zapConfig)
if err != nil {
return nil, fmt.Errorf("failed to open logger config file")
}
defer file.Close()
var cfg zap.Config
if err := json.NewDecoder(file).Decode(&cfg); err != nil {
return nil, fmt.Errorf("failed to parse logger config json")
}
logger, err := cfg.Build()
if err != nil {
return nil, err
}
defer logger.Sync()
logger.Debug("logger construction succeeded")
return logger, nil
}
这里,NewLogger 函数将配置文件路径(zapConfig)作为输入,并返回一个 zap.Logger 实例。这是构造函数注入的一个示例,依赖关系(日志记录器配置)被注入到函数中。
1.2. 数据库连接设置
第二个文件使用 gorm 库处理数据库连接。它定义了一个接口 Db 和两个实现(PostgresAdapter 和 MySQLAdapter),用于连接 PostgreSQL 和 MySQL 数据库。
type Db interface {
MakeConnection() (*gorm.DB, error)
}
func NewDBConnectionAdapter(dbName, url string, dbMaxIdle, dbMaxOpen, dbMaxLifeTime, dbMaxIdleTime int, gormConf string) Db {
switch dbName {
case Postgres:
return &PostgresAdapter{dbUrl: url, dbMaxIdle: dbMaxIdle, dbMaxOpen: dbMaxOpen, dbMaxLifeTime: dbMaxLifeTime, dbMaxIdleTime: dbMaxIdleTime, gormConf: gormConf}
case Mysql:
return &MySQLAdapter{dbUrl: url, dbMaxIdle: dbMaxIdle, dbMaxOpen: dbMaxOpen, dbMaxLifeTime: dbMaxLifeTime, dbMaxIdleTime: dbMaxIdleTime, gormConf: gormConf}
}
return &PostgresAdapter{dbUrl: url, dbMaxIdle: dbMaxIdle, dbMaxOpen: dbMaxOpen, dbMaxLifeTime: dbMaxLifeTime, dbMaxIdleTime: dbMaxIdleTime, gormConf: gormConf}
}
NewDBConnectionAdapter 函数作为一个工厂,根据 dbName 参数创建适当的数据库适配器。这是工厂注入的一个示例,由工厂决定注入哪个实现。
1.3. 管理依赖关系的容器
第三个文件定义了容器接口及其实现。容器负责管理所有依赖项(日志记录器、数据库等),并在需要时注入它们。
type Container interface {
Logger() *zap.Logger
Db() *gorm.DB
Port() string
PprofEnable() string
}
type container struct {
logger *zap.Logger
db *gorm.DB
port string
pprofEnable string
environmentVariables map[string]string
}
func New(envVars map[string]string) (Container, error) {
c := &container{environmentVariables: envVars}
var err error
c.db, err = c.dbSetup()
if err != nil {
return c, err
}
c.logger, err = c.loggerSetup()
if err != nil {
return c, err
}
c.port, err = c.portSetup()
if err != nil {
return c, err
}
c.pprofEnable, err = c.pprofEnableSetup()
if err != nil {
return c, err
}
return c, nil
}
New 函数通过设置所有依赖关系来初始化容器。它使用构造函数注入将环境变量和配置传递给容器。每个依赖项(日志记录器、数据库等)都是单独初始化的,从而使代码模块化并易于测试。
本示例中依赖注入的主要优点:
-
松耦合:容器不会直接创建其依赖关系。相反,它依赖外部配置和工厂来提供这些依赖。这使得代码更灵活、更易于修改。
-
可测试性:由于依赖关系是注入的,因此在测试过程中可以轻松地模拟它们。例如,您可以在单元测试中用模拟数据库替换真实数据库连接。
-
单一责任原则:每个组件(日志记录器、数据库适配器等)都有单一责任。容器只负责管理依赖关系,而不是创建依赖关系。
-
可重用性:数据库接口及其实现可在应用程序的不同部分重复使用。你可以在 PostgreSQL 和 MySQL 之间切换,而无需改变核心逻辑。
2、如何使用容器
下面介绍如何在应用程序中使用容器:
func main() {
c, err := container.New(map[string]string{
container.LogLevelEnvVar: os.Getenv(container.LogLevelEnvVar),
container.DatabaseURLEnvVar: os.Getenv(container.DatabaseURLEnvVar),
container.PortEnvVar: os.Getenv(container.PortEnvVar),
container.DBMaxIdleEnvVar: os.Getenv(container.DBMaxIdleEnvVar),
container.DBMaxOpenEnvVar: os.Getenv(container.DBMaxOpenEnvVar),
container.DBMaxLifeTimeEnvVar: os.Getenv(container.DBMaxLifeTimeEnvVar),
container.DBMaxIdleTimeEnvVar: os.Getenv(container.DBMaxIdleTimeEnvVar),
container.ZapConf: os.Getenv(container.ZapConf),
container.GormConf: os.Getenv(container.GormConf),
container.PprofEnable: os.Getenv(container.PprofEnable),
})
if err != nil {
defer func() {
fmt.Println("server initialization failed error: %w", err)
}()
panic("server initialization failed")
}
logger := c.Logger()
db := c.Db()
logger.Info("Application started", zap.String("port", c.Port()))
// Use db and logger in your application...
}
3、结论
依赖注入模式是构建模块化、可测试和可维护应用程序的强大工具。在这个示例中,我们看到了如何在 Go 中使用接口、工厂和容器来管理依赖关系,从而实现依赖注入。
通过采用 DI,您可以:
- 解耦应用程序的组件。
- 提高可测试性。
- 使你的代码更具可重用性和可维护性。
如果你是依赖注入的新手,鼓励你在自己的项目中尝试实施依赖注入。从小处着手,逐步重构代码,在合理的地方使用依赖注入。