首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在不同的包中使用方法的接口

在不同的包中使用方法的接口
EN

Stack Overflow用户
提问于 2021-02-24 12:32:19
回答 1查看 480关注 0票数 1

我正在尝试理解接口的使用方式。下面是一个人为的例子来说明我的问题。我有一个主包,它实例化一个测试数据库,然后将它传递给测试服务器,然后在那里初始化服务器。

然后是对服务器的调用,它执行虚拟数据库插入(使用虚拟数据库依赖项,在服务器初始化时传递)。

main.go

代码语言:javascript
复制
package main

import (
    "interfaces/database"
    "interfaces/server"
)


func main() {
    db := database.Start()
    s := server.Start(db)
    s.HandleInsert()
}

database.go

代码语言:javascript
复制
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

代码语言:javascript
复制
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

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-02-24 13:16:16

乍一看,我会说你是在用惯用的方式做事:

  • 返回类型(数据库Start返回*Database,正如它应该的那样)--
  • --包为其依赖项定义接口(正如您正在做的那样)

接口的大小并不取决于给定类型导出的内容(即您的database.Database类型实现了什么方法),而是您需要什么功能。如果您的server包只需要使用SelectInsertDelete,那么接口应该是这样的。传递给服务器包的类型可以实现SelectNASASecretLizardFiles,但是如果您不使用它,则server包不必知道该方法是否存在。server.Database接口仍然像现在一样简单。

从本质上讲,这就是小界面的含义。Golang接口是隐式实现的(有时人们称它为ducktype接口)。实现在server.Database中定义的3种方法的任何类型都可以用作依赖项。这使得您的包非常容易进行单元测试(嘲弄是微不足道的)。

“缺点”可能是,如果您有几个包(取决于Database类型),您可能会得到相同接口的重复定义。但是,如果其中一个包需要访问其他函数(或者不需要使用Insert方法),则更改该包的接口不会影响任何其他包。这符合整个概念,金刚包是独立的。

不过,在你的特殊情况下,我认为还有作出判决的余地。如果您正在与数据库进行接口,我认为这是一个公平的假设,即大多数(如果不是全部)包都需要能够选择数据。常见的情况是,在公共包中定义了一个小型的基本接口:

代码语言:javascript
复制
|
|-- 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)

接口如下所示:

代码语言:javascript
复制
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的工具。您可以为每个包的接口生成单元测试的模拟,方法是添加一个这样的注释:

代码语言:javascript
复制
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的类型只是代码嗅觉。口吃的名字应该避免。也许调用句柄HandleConn更有意义:db.Handledb.Conn可以更好地描述实际处理的内容,并且可以更短地输入。

获得DB连接的函数也很奇怪地命名(Start)。它是一个构造函数,所以我认为称它为New更有意义,结果是代码:

代码语言:javascript
复制
db := database.New()
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66351011

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档