我正在尝试理解接口的使用方式。下面是一个人为的例子来说明我的问题。我有一个主包,它实例化一个测试数据库,然后将它传递给测试服务器,然后在那里初始化服务器。
然后是对服务器的调用,它执行虚拟数据库插入(使用虚拟数据库依赖项,在服务器初始化时传递)。
main.go
package main
import (
"interfaces/database"
"interfaces/server"
)
func main() {
db := database.Start()
s := server.Start(db)
s.HandleInsert()
}database.go
package database
import "fmt"
type Database struct {
pool string
}
func Start() *Database{
database := &Database{}
database.pool = "examplepool"
return database
}
func(db *Database) Select() {
fmt.Println("Running a Select")
}
func(db *Database) Insert() {
fmt.Println("Running an Insert")
}
func (db *Database) Delete() {
fmt.Println("Running a Delete")
}server.go
package server
import "fmt"
type Database interface {
Select()
Insert()
Delete()
}
type Server struct {
server string
db Database
}
func Start(db Database) *Server {
fmt.Println("Created Server")
s := &Server{"exampleserver", db}
return s
}
func(s *Server) HandleInsert() {
s.db.Insert()
}问题是,在服务器包中,为了使用数据库包,我必须写出数据库对象所拥有的所有方法。我只有三种方法,但我的数据库对象可以很容易地获得更多。这与Go的小接口哲学背道而驰。我在这里错过了什么?我不想将数据库包导入到服务器包中,因为我希望尽可能地封装每个包。
另一个问题是,假设我有其他希望使用此数据库包的包。它们是否也包含类似的数据库接口?我是否应该有一个名为“接口”的包,它包含数据库接口,然后可以导入?
这个代码布局的想法来自于这个视频:https://youtu.be/rWBSMsLG8po
发布于 2021-02-24 13:16:16
乍一看,我会说你是在用惯用的方式做事:
Start返回*Database,正如它应该的那样)--接口的大小并不取决于给定类型导出的内容(即您的database.Database类型实现了什么方法),而是您需要什么功能。如果您的server包只需要使用Select、Insert和Delete,那么接口应该是这样的。传递给服务器包的类型可以实现SelectNASASecretLizardFiles,但是如果您不使用它,则server包不必知道该方法是否存在。server.Database接口仍然像现在一样简单。
从本质上讲,这就是小界面的含义。Golang接口是隐式实现的(有时人们称它为ducktype接口)。实现在server.Database中定义的3种方法的任何类型都可以用作依赖项。这使得您的包非常容易进行单元测试(嘲弄是微不足道的)。
“缺点”可能是,如果您有几个包(取决于Database类型),您可能会得到相同接口的重复定义。但是,如果其中一个包需要访问其他函数(或者不需要使用Insert方法),则更改该包的接口不会影响任何其他包。这符合整个概念,金刚包是独立的。
不过,在你的特殊情况下,我认为还有作出判决的余地。如果您正在与数据库进行接口,我认为这是一个公平的假设,即大多数(如果不是全部)包都需要能够选择数据。常见的情况是,在公共包中定义了一个小型的基本接口:
|
|-- server
| |
| |--> dependencies.go (defines Databse interface for server pkg)
|
|-- foo
| |
| |--> dependencies.go (defines Database interface for this package)
|
|-- common (bad package name, but self explanatory)
| |
| |--> database.go (defines common subset of database interfaces)接口如下所示:
package common
type DB interface {
// don't return a slice of maps, this is just an example
Select(query string, args ...interface{}) (rows []map[string]interface{}, err error)
Close() error
}
package server
import "your.project/common"
type Database interface {
common.DB // embedded common interface
Insert(query string, vals ...interface{}) error
Delete(query, id string) error
}这是一种常见的构造代码的方法,同时确保了简单的模拟和测试。
说到嘲弄/测试,只是一个提示,但是看看一个叫做mockgen的工具。您可以为每个包的接口生成单元测试的模拟,方法是添加一个这样的注释:
package server
import "your.project/common"
//go:generate go run github.com/golang/mock/mockgen -destination mocks/db_mock.go -package mocks your.project/server Database
type Database interface {
common.DB // embedded common interface
Insert(query string, vals ...interface{}) error
Delete(query, id string) error
}运行go generate将显示出您可以在单元测试中导入的模拟。
其他评论意见
我不得不注意到的是,您的database包声明了一个名为Database的类型。为什么输出该类型?为什么它和包的名字是一样的?使用一种名为database.Database的类型只是代码嗅觉。口吃的名字应该避免。也许调用句柄Handle或Conn更有意义:db.Handle或db.Conn可以更好地描述实际处理的内容,并且可以更短地输入。
获得DB连接的函数也很奇怪地命名(Start)。它是一个构造函数,所以我认为称它为New更有意义,结果是代码:
db := database.New()https://stackoverflow.com/questions/66351011
复制相似问题