草庐IT

go - 我怎样才能使这个对象映射在 Go 中更干燥和可重用?

coder 2024-07-10 原文

我在 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
}

sql.PlaceholderPairs() 函数如下所示:

func PlaceholderPairs(i interface{}) string {

    s := ""
    val := reflect.ValueOf(i).Elem()
    count := val.NumField()

    for i := 0; i < count; i++ {
        typeField := val.Type().Field(i)
        tag := typeField.Tag

        fname := strings.ToLower(typeField.Name)

        if fname == "id" {
            continue
        }

        if t := tag.Get("db"); t == "-" {
            continue
        } else if t != "" {
            s += t + "=:" + t
        } else {
            s += fname + "=:" + fname
        }
        s += ", "
    }
    s = s[:len(s)-2]
    return s
}

但是每次我创建一个新的结构,例如一个用户结构,我必须复制粘贴上面的“crud 部分”并创建一个 user_crud.go 文件,并将“消息”替换为“用户”,以及“消息”与“用户”。我重复了很多代码,它不是很枯燥。有什么我可以做的,而不是为我会重用的东西重复这段代码吗?我总是有一个 save() 方法,并且总是有一个 ByUnique() 函数,我可以在其中返回一个结构并按唯一列进行搜索。

在 PHP 中这很容易,因为 PHP 不是静态类型的。

这在 Go 中可以做到吗?

最佳答案

您的 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 中,通常最好稍微重复一些简单的代码,而不是创建复杂的代码来避免它。


编辑:如果您想使用接口(interface)实现Save,那没问题。只需创建一个 Identifier 接口(interface)即可。

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 的调用。我不知道您使用的是什么包,如果您向它传递接口(interface)而不是实际结构,Exec 可能无法正常工作,但它可能会工作。也就是说,我可能只是传递标识符而不是添加此开销。

关于go - 我怎样才能使这个对象映射在 Go 中更干燥和可重用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35260122/

有关go - 我怎样才能使这个对象映射在 Go 中更干燥和可重用?的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  4. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  5. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  6. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

    我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

  7. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

  8. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

    如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

  9. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  10. ruby - 更改 ActiveRecord 中对象的类 - 2

    假设我有一个FireNinja我的数据库中的对象,使用单表继承存储。后来才知道他真的是WaterNinja.将他更改为不同的子类的最干净的方法是什么?更好的是,我很想创建一个新的WaterNinja对象并替换旧的FireNinja在数据库中,保留ID。编辑我知道如何创建新的WaterNinja来self现有FireNinja的对象,我也知道我可以删除旧的并保存新的。我想做的是改变现有项目的类别。我是通过创建一个新对象并执行一些ActiveRecord魔法来替换行,还是通过对对象本身做一些疯狂的事情,或者甚至通过删除它并使用相同的ID重新插入来做到这一点,这是问题的一部分。

随机推荐