我尝试在Go app(简单的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#中找到类似Generics的东西,但没找到。
任何人都可以帮助我吗?
答案 0 :(得分:19)
不,Go does not have generics并且在可预见的未来不会有它们。
您有三种选择:
重构您的代码,以便您拥有一个接受SQL语句和另一个函数的函数,并且:
在这种情况下,您将拥有一个通用的“查询”功能, 差异仅在于“扫描”功能。
有可能有几种变化,但我怀疑你有这个想法。
使用sqlx
包基本上是SQL驱动的数据库{J}数据流的encoding/json
:它使用类型的反射来创建和执行SQL来填充它们。< / p>
这样你就可以在另一个层面上获得可重用性:你根本就不会编写样板代码。
使用code generation Go-native方式拥有“代码模板”(这就是泛型的含义)。
这样,您(通常)编写一个Go程序,它接受一些输入(以您希望的任何格式),读取并写出一个或多个包含Go代码的文件,然后编译。
在您非常简单的情况下,您可以从Go函数的模板开始,以及某种表格,该表格将SQL语句映射到要从所选数据创建的类型。
我注意到你的代码确实看起来非常不合时宜。
没有一个正确的人在Go中实现“存储库模式”,但是这很好,只要它让你开心 - 我们都在某种程度上被我们习惯的语言/环境灌输,但是你的connection := r.provider.GetConnection()
看起来很惊人:Go database/sql
与“热门”环境和框架截然不同,因此我强烈建议您从this和this开始。
答案 1 :(得分:2)
如果我误解了,请原谅我,但更好的模式可能如下:
type RepositoryItem interface {
Name() string // for example
}
type Repository interface {
GetAll() ([]RepositoryItem, error)
}
目前,您基本上为每种类型的存储库都有多个接口,因此,除非您要实现多种类型的RoleRepository
,否则您可能没有接口。
从长远来看,拥有通用Repository
和RepositoryItem
接口可能会使您的代码更具可扩展性(更不用说更容易测试)。
一个人为的例子可能是(如果我们假设Repository
模糊地与后端相关联),例如MySQLRepository
和MongoDBRepository
。通过抽象存储库的功能,您可以防止未来的突变。
但我非常建议你看看@ kostix的答案。
答案 2 :(得分:1)
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