更多个人笔记见:
(注意点击“继续”,而不是“发现新项目”)
github个人笔记仓库 https://github.com/ZHLOVEYY/IT_note
gitee 个人笔记仓库 https://gitee.com/harryhack/it_note
个人学习,学习过程中还会不断补充~ (后续会更新在github上)
例子分析依赖注入
- 依赖注入(DI)是一种设计模式,通过外部传入依赖对象而非内部创建
- 解耦是目标,DI是实现手段
传统方式
//传统方式(紧耦合)
func GetUser(id uint) {
db := gorm.Open(...) // 内部创建依赖
db.First(...)
}
func GetUser(db *gorm.DB, id uint) { // 依赖从外部注入
db.First(...)
}
DI方式
真正的依赖注入应该: 依赖抽象而非具体实现,通过接口解耦并便于单元测试
// DI方式(松耦合)
// 1. 定义接口(抽象层)
type Repository interface{...}
// 2. 实现具体存储库
type GormRepository struct{...}
// 3. 业务服务声明依赖接口
type UserService struct {
repo Repository
}
// 4. 依赖注入点(通常在main/初始化代码)
func main() {
gormDB := initGorm() // 初始化具体DB连接,这个地方是初始化,自己定义的
// 将具体实现注入抽象接口!!!!!!
service := UserService{
repo: &GormRepository{db: gormDB}
}
//接下来 UserService的方法就可以实现直接调用->进一步调用GormRepository(也就是满足Repository接口的结构体的具体方法)
}
具体例子分析:
// 定义数据模型
type User struct {
ID uint
Name string
Email string
Password string
}
// 1. 定义接口(抽象层)
type Repository interface {
FindUserByID(id uint) (*User, error)
CreateUser(user *User) error
UpdateUser(user *User) error
DeleteUser(id uint) error
}
// 2. 实现具体存储库
type GormRepository struct {
db *gorm.DB
}
// 实现Repository接口的方法
func (r *GormRepository) FindUserByID(id uint) (*User, error) {
var user User
result := r.db.First(&user, id)
if result.Error != nil {
return nil, result.Error
}
return &user, nil
}
func (r *GormRepository) CreateUser(user *User) error {
return r.db.Create(user).Error
}
func (r *GormRepository) UpdateUser(user *User) error {
return r.db.Save(user).Error
}
func (r *GormRepository) DeleteUser(id uint) error {
return r.db.Delete(&User{}, id).Error
}
// 3. 业务服务声明依赖接口
type UserService struct {
repo Repository
}
// 业务方法使用注入的Repository 这里不是需要满足Repository,都是实际业务方法
//repo 只是向下进一步调用
func (s *UserService) GetUser(id uint) (*User, error) {
return s.repo.FindUserByID(id) //调用对应的FindUserByID方法
}
func (s *UserService) RegisterUser(name, email, password string) (*User, error) {
user := &User{
Name: name,
Email: email,
Password: password, // 实际应用中应该加密
}
err := s.repo.CreateUser(user)
if err != nil {
return nil, fmt.Errorf("failed to register user: %w", err)
}
return user, nil
}
func (s *UserService) UpdateUserProfile(id uint, name string) error {
user, err := s.repo.FindUserByID(id)
if err != nil {
return fmt.Errorf("user not found: %w", err)
}
user.Name = name
return s.repo.UpdateUser(user)
}
// 4. 依赖注入点(通常在main/初始化代码)
func main() {
// 初始化数据库连接,这里是可以修改的
gormDB := initGorm()
// 将具体实现注入抽象接口,而不是内部使用具体的
userService := &UserService{
repo: &GormRepository{db: gormDB},
}
// 使用服务
user, err := userService.GetUser(1)
if err != nil {
fmt.Printf("Error getting user: %v\n", err)
return
}
fmt.Printf("Found user: %s (%s)\n", user.Name, user.Email)
// 注册新用户
newUser, err := userService.RegisterUser("John Doe", "john@example.com", "password123")
if err != nil {
fmt.Printf("Error registering user: %v\n", err)
return
}
fmt.Printf("Registered new user with ID: %d\n", newUser.ID)
}
// 初始化GORM数据库连接,对应 main 一开始
func initGorm() *gorm.DB {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 自动迁移
db.AutoMigrate(&User{})
return db
}
构造函数注入依赖
在 main 中注入的时候可以修改:
// 使用构造函数注入依赖
func NewUserService(repo Repository) *UserService {
return &UserService{
repo: repo,
}
}
// 使用示例
func main() {
gormDB := initGorm()
repo := &GormRepository{db: gormDB}
// 通过构造函数注入依赖
userService := NewUserService(repo)
// 使用服务...
}
在每一层都可以使用函数方式的注入,所以代码中能看到很多“New”
容器式注入
// 简单的DI容器
type Container struct {
services map[string]interface{}
}
func NewContainer() *Container {
return &Container{
services: make(map[string]interface{}),
}
}
func (c *Container) Register(name string, service interface{}) {
c.services[name] = service
}
func (c *Container) Get(name string) interface{} {
return c.services[name]
}
// 使用容器
func main() {
container := NewContainer()
// 注册服务
gormDB := initGorm()
container.Register("repository", &GormRepository{db: gormDB})
container.Register("userService", NewUserService(container.Get("repository").(Repository)))
// 获取服务
userService := container.Get("userService").(*UserService)
// 使用服务...
}
(补充)Mock测试
// 用于测试的Mock存储库
type MockRepository struct {
users map[uint]*User
}
//构造函数但是没有注入依赖,因为只是初始化了一个内部状态(`users` 映射),但没有接收任何外部依赖
func NewMockRepository() *MockRepository {
return &MockRepository{
users: make(map[uint]*User), //**初始时确实没有存储任何用户数据**
}
}
func (r *MockRepository) FindUserByID(id uint) (*User, error) {
user, exists := r.users[id]
if !exists {
return nil, fmt.Errorf("user not found")
}
return user, nil
}
func (r *MockRepository) CreateUser(user *User) error {
if user.ID == 0 {
user.ID = uint(len(r.users) + 1)
}
r.users[user.ID] = user
return nil
}
func (r *MockRepository) UpdateUser(user *User) error {
if _, exists := r.users[user.ID]; !exists {
return fmt.Errorf("user not found")
}
r.users[user.ID] = user
return nil
}
func (r *MockRepository) DeleteUser(id uint) error {
if _, exists := r.users[id]; !exists {
return fmt.Errorf("user not found")
}
delete(r.users, id)
return nil
}
// 测试示例,这里用到了 GO 中的测试!
func TestUserService() {
// 使用Mock存储库进行测试
mockRepo := NewMockRepository() //利用函数初始化
userService := &UserService{repo: mockRepo} //mockRepo是满足对应的 Repository 接口的方法的
//接下来可以使用UserService 的结构体方法了
// 测试注册用户
user, _ := userService.RegisterUser("Test User", "test@example.com", "password")
// 测试获取用户
foundUser, _ := userService.GetUser(user.ID) //GetUser中会调用mockRepo的方法的
fmt.Printf("Found user in test: %s\n", foundUser.Name)
}