猿问

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

我在 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 中做到吗?


RISEBY
浏览 190回答 3
3回答

拉风的咖菲猫

你的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如果你传递一个接口而不是实际的结构,它可能无法正常工作,但它可能会工作。也就是说,我可能只是传递标识符而不是添加这个开销。

慕丝7291255

您可能想要使用 ORM。它们消除了您描述的许多样板代码。见这个问题对于“什么是ORM?”以下是 go 的 ORM 列表:https : //github.com/avelino/awesome-go#orm我自己从来没有用过一个,所以我不能推荐任何一个。主要原因是 ORM 需要开发人员的大量控制并引入不可忽略的性能开销。您需要亲眼看看它们是否适合您的用例和/或您是否对这些库中正在发生的“魔法”感到满意。

HUX布斯

我不建议这样做,我个人更喜欢明确地扫描结构和创建查询。但如果你真的想坚持反思,你可以这样做:func ByUnique(obj interface{}, column string, value interface{}) error {    // ...    return sql.DB.QueryRowx(query, value).StructScan(obj)}// Call withmessage := &Message{}ByUnique(message, ...)为了您的保存:type Identifiable interface {    Id() int64}// Implement Identifiable for message, etc.func Save(obj Identifiable) error {    // ...}// Call withSave(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}这具有使用类型系统并仅映射它应该实际映射的内容的优点。
随时随地看视频慕课网APP

相关分类

Go
我要回答