为了账号安全,请及时绑定邮箱和手机立即绑定

Golang 仓库模式

Golang 仓库模式

Go
阿波罗的战车 2021-11-22 19:27:27
我尝试在 Go 应用程序(简单的 Web 服务)中实现存储库模式,并尝试找到更好的方法来避免代码重复。这是一个代码接口是:type IRoleRepository interface {    GetAll() ([]Role, error)}type ISaleChannelRepository interface {    GetAll() ([]SaleChannel, error)}和实施:func (r *RoleRepository) GetAll() ([]Role, error) {        var result []Role        var err error        var rows *sql.Rows        if err != nil {            return result, err        }        connection := r.provider.GetConnection()        defer connection.Close()        rows, err = connection.Query("SELECT Id,Name FROM Position")        defer rows.Close()        if err != nil {            return result, err        }        for rows.Next() {            entity := new(Role)            err = sqlstruct.Scan(entity, rows)            if err != nil {                return result, err            }            result = append(result, *entity)        }        err = rows.Err()        if err != nil {            return result, err        }        return result, err    }    func (r *SaleChannelRepository) GetAll() ([]SaleChannel, error) {        var result []SaleChannel        var err error        var rows *sql.Rows        if err != nil {            return result, err        }        connection := r.provider.GetConnection()        defer connection.Close()        rows, err = connection.Query("SELECT DISTINCT SaleChannel 'Name' FROM Employee")        defer rows.Close()        if err != nil {            return result, err        }        for rows.Next() {            entity := new(SaleChannel)            err = sqlstruct.Scan(entity, rows)            if err != nil {                return result, err            }            result = append(result, *entity)        }        err = rows.Err()        if err != nil {            return result, err        }        return result, err    }如您所见,差异仅在于几句话。我试图从 C# 中找到类似泛型的东西,但没有找到。谁能帮我?
查看完整描述

3 回答

?
墨色风雨

TA贡献1853条经验 获得超6个赞

不,Go 没有泛型并且在可预见的未来不会有它们¹。

您有三个选择:

  • 重构您的代码,以便您拥有一个接受 SQL 语句和另一个函数的函数,并且:

    在这种情况下,您将拥有一个通用的“查询”功能,区别仅在于“扫描”功能。

    这方面的几种变化是可能的,但我怀疑你有这个想法。

    1. 使用提供的语句查询数据库。

    2. 迭代结果的行。

    3. 对于每一行,调用提供的函数,其任务是扫描该行。

  • 使用sqlx基本上是 SQL 驱动的数据库的包,什么encoding/json是 JSON 数据流:它使用对您的类型的反射来创建和执行 SQL 来填充它们。

    通过这种方式,您将在另一个层面上获得可重用性:您根本不会编写样板代码。

  • 使用代码生成,这是拥有“代码模板”Go-native 方式(这就是泛型的意义所在)。

    通过这种方式,您(通常)编写一个 Go 程序,它接受一些输入(以您希望的任何格式),读取它并写出一个或多个包含 Go 代码的文件,然后编译这些文件。

    在您的非常简单的情况下,您可以从 Go 函数的模板和某种表开始,该表将 SQL 语句映射到要从所选数据创建的类型。


我会注意到您的代码确实看起来非常单调。

没有头脑正常的人会在 Go 中实现“存储库模式”,但是只要它能让你开心就好了——我们都在一定程度上被灌输了我们习惯的语言/环境——但是你的connection := r.provider.GetConnection()样子令人担忧:Godatabase/sql与“流行”的环境和框架截然不同,所以我强烈建议从thisthis开始。


¹(截至 2021 年 5 月 31 日更新)Go 将具有泛型,因为实现它们的提案已被接受,并且实现它们的工作正在进行中。


查看完整回答
反对 回复 2021-11-22
?
守候你守候我

TA贡献1802条经验 获得超10个赞

如果我误解了,请原谅我,但更好的模式可能如下所示:


type RepositoryItem interface {

    Name() string // for example

}


type Repository interface {

    GetAll() ([]RepositoryItem, error)

}

目前,对于每种类型的存储库,您本质上都有多个接口,因此除非您要实现多种类型的RoleRepository,否则您可能没有该接口。


从长远来看,拥有泛型Repository和RepositoryItem接口可能会使您的代码更具可扩展性(更不用说更容易测试了)。


一个人为的例子可能是(如果我们假设 aRepository与后端模糊地相关)诸如MySQLRepository和 之类的实现MongoDBRepository。通过抽象存储库的功能,您可以防止将来发生突变。



查看完整回答
反对 回复 2021-11-22
?
慕桂英546537

TA贡献1848条经验 获得超10个赞

interface{}是 Go 中的“通用类型”。我可以想象做这样的事情:


package main


import "fmt"


type IRole struct {

    RoleId uint

}


type ISaleChannel struct {

    Profitable bool

}


type GenericRepo interface{

    GetAll([]interface{})

}


// conceptual repo to store all Roles and SaleChannels

type Repo struct {

    IRoles        []IRole

    ISaleChannels []ISaleChannel

}


func (r *Repo) GetAll(ifs []interface{}) {


    // database implementation here before type switch 


    for _, v := range ifs {

        switch v := v.(type) {

        default:

                fmt.Printf("unexpected type %T\n", v)

        case IRole:

                fmt.Printf("Role %t\n", v)            

                r.IRoles = append(r.IRoles, v)

        case ISaleChannel:

                fmt.Printf("SaleChannel %d\n", v)

                r.ISaleChannels = append(r.ISaleChannels, v)

        }

    }

}


func main() {


    getter := new(Repo)


    // mock slice

    data := []interface{}{

        IRole{1},

        IRole{2},

        IRole{3},

        ISaleChannel{true},

        ISaleChannel{false},

        IRole{4},

    }


    getter.GetAll(data)


    fmt.Println("IRoles: ", getter.IRoles)

    fmt.Println("ISaleChannels: ", getter.ISales)

}

通过这种方式,您不必以两个结构和/或接口结束IRole和ISale


查看完整回答
反对 回复 2021-11-22
  • 3 回答
  • 0 关注
  • 280 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信