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

如何使这个对象映射在 Go 中更加干燥和可重用?

如何使这个对象映射在 Go 中更加干燥和可重用?

Go
RISEBY 2021-12-07 19:31:37
我在 Go 中创建了一个非关系的对象映射,它非常简单。我有几个看起来像这样的结构:type Message struct {    Id       int64    Message  string    ReplyTo  sql.NullInt64 `db:"reply_to"`    FromId   int64         `db:"from_id"`    ToId     int64         `db:"to_id"`    IsActive bool          `db:"is_active"`    SentTime int64         `db:"sent_time"`    IsViewed bool          `db:"is_viewed"`    Method   string `db:"-"`    AppendTo int64  `db:"-"`}要创建新消息,我只需运行此函数:func New() *Message {    return &Message{        IsActive: true,        SentTime: time.Now().Unix(),        Method:   "new",    }}然后我有一个用于这个结构的 message_crud.go 文件,如下所示:要按唯一列(例如按 id)查找消息,我运行此函数:func ByUnique(column string, value interface{}) (*Message, error) {    query := fmt.Sprintf(`        SELECT *        FROM message        WHERE %s = ?        LIMIT 1;    `, column)    message := &Message{}    err := sql.DB.QueryRowx(query, value).StructScan(message)    if err != nil {        return nil, err    }    return message, nil}为了保存消息(在数据库中插入或更新),我运行了这个方法:func (this *Message) save() error {    s := ""    if this.Id == 0 {        s = "INSERT INTO message SET %s;"    } else {        s = "UPDATE message SET %s WHERE id=:id;"    }    query := fmt.Sprintf(s, sql.PlaceholderPairs(this))    nstmt, err := sql.DB.PrepareNamed(query)    if err != nil {        return err    }    res, err := nstmt.Exec(*this)    if err != nil {        return err    }    if this.Id == 0 {        lastId, err := res.LastInsertId()        if err != nil {            return err        }        this.Id = lastId    }    return nil}但是每次我创建一个新的结构,例如一个用户结构时,我必须复制粘贴上面的“crud 部分”并创建一个 user_crud.go 文件并将“消息”替换为“用户”,并将“消息”替换为“消息”与“用户”。我重复了很多代码,它不是很枯燥。有什么我可以做的事情,我可以不为我会重用的东西重复这段代码吗?我总是有一个 save() 方法,并且总是有一个 ByUnique() 函数,我可以在其中返回一个结构并按唯一的列进行搜索。在 PHP 中这很容易,因为 PHP 不是静态类型的。这可以在 Go 中做到吗?
查看完整描述

3 回答

?
拉风的咖菲猫

TA贡献1995条经验 获得超2个赞

你的ByUnique已经几乎是通用的了。只需拉出变化的部分(表格和目的地):


func ByUnique(table string, column string, value interface{}, dest interface{}) error {

    query := fmt.Sprintf(`

            SELECT *

            FROM %s

            WHERE %s = ?

            LIMIT 1;

        `, table, column)


    return sql.DB.QueryRowx(query, value).StructScan(dest)

}


func ByUniqueMessage(column string, value interface{}) (*Message, error) {

    message := &Message{}

    if err := ByUnique("message", column, value, &message); err != nil {

        return nil, err

    }

    return message, error

}

你save的非常相似。您只需要按照以下方式创建一个通用的保存功能:


func Save(table string, identifier int64, source interface{}) { ... }

然后在 内部(*Message)save,您只需调用通用Save()函数即可。看起来很简单。


旁注:不要this在方法内用作对象的名称。有关更多信息,请参阅@OneOfOne 的链接。不要沉迷于 DRY。它本身并不是一个目标。Go 专注于代码简单、清晰和可靠。不要为了避免输入简单的错误处理行而创建复杂和脆弱的东西。这并不意味着您不应该提取重复的代码。这只是意味着在 Go 中,通常最好重复一点简单的代码,而不是创建复杂的代码来避免它。


编辑:如果你想Save使用接口来实现,那没问题。只需创建一个Identifier接口。


type Ider interface {

    Id() int64

    SetId(newId int64)

}


func (msg *Message) Id() int64 {

    return msg.Id

}


func (msg *Message) SetId(newId int64) {

    msg.Id = newId

}


func Save(table string, source Ider) error {

    s := ""

    if source.Id() == 0 {

        s = fmt.Sprintf("INSERT INTO %s SET %%s;", table)

    } else {

        s = fmt.Sprintf("UPDATE %s SET %%s WHERE id=:id;", table)

    }

    query := fmt.Sprintf(s, sql.PlaceholderPairs(source))


    nstmt, err := sql.DB.PrepareNamed(query)

    if err != nil {

        return err

    }


    res, err := nstmt.Exec(source)

    if err != nil {

        return err

    }


    if source.Id() == 0 {

        lastId, err := res.LastInsertId()

        if err != nil {

            return err

        }

        source.SetId(lastId)

    }


    return nil

}


func (msg *Message) save() error {

    return Save("message", msg)

}

可能会因此而爆炸的一个部分是调用Exec. 我不知道你使用的是什么包,Exec如果你传递一个接口而不是实际的结构,它可能无法正常工作,但它可能会工作。也就是说,我可能只是传递标识符而不是添加这个开销。


查看完整回答
反对 回复 2021-12-07
?
慕丝7291255

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

您可能想要使用 ORM。它们消除了您描述的许多样板代码。

这个问题对于“什么是ORM?”

以下是 go 的 ORM 列表:https : //github.com/avelino/awesome-go#orm

我自己从来没有用过一个,所以我不能推荐任何一个。主要原因是 ORM 需要开发人员的大量控制并引入不可忽略的性能开销。您需要亲眼看看它们是否适合您的用例和/或您是否对这些库中正在发生的“魔法”感到满意。


查看完整回答
反对 回复 2021-12-07
?
HUX布斯

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

我不建议这样做,我个人更喜欢明确地扫描结构和创建查询。


但如果你真的想坚持反思,你可以这样做:


func ByUnique(obj interface{}, column string, value interface{}) error {

    // ...

    return sql.DB.QueryRowx(query, value).StructScan(obj)

}


// Call with

message := &Message{}

ByUnique(message, ...)

为了您的保存:


type Identifiable interface {

    Id() int64

}


// Implement Identifiable for message, etc.


func Save(obj Identifiable) error {

    // ...

}


// Call with

Save(message)

我使用并推荐给您的方法:


type Redirect struct {

    ID        string

    URL       string

    CreatedAt time.Time

}


func FindByID(db *sql.DB, id string) (*Redirect, error) {

    var redirect Redirect


    err := db.QueryRow(

        `SELECT "id", "url", "created_at" FROM "redirect" WHERE "id" = $1`, id).

        Scan(&redirect.ID, &redirect.URL, &redirect.CreatedAt)


    switch {

    case err == sql.ErrNoRows:

        return nil, nil

    case err != nil:

        return nil, err

    }


    return &redirect, nil

}


func Save(db *sql.DB, redirect *Redirect) error {

    redirect.CreatedAt = time.Now()


    _, err := db.Exec(

        `INSERT INTO "redirect" ("id", "url", "created_at") VALUES ($1, $2, $3)`,

        redirect.ID, redirect.URL, redirect.CreatedAt)


    return err

}

这具有使用类型系统并仅映射它应该实际映射的内容的优点。


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

添加回答

举报

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