避免在 Go 中编写过多的 getter 和 setter

Avoid writing too many getters and setters in Go

我正在用 Go 实现一个消息传递系统。所以我有一个通用接口,叫做 MsgMsg 接口定义了许多公共字段,例如源、目标、发送时间、接收时间等。我无法定义 Msg 的完整列表,因为我希望库用户定义具体类型Msg秒。

要提供 Msg 的具体类型,用户需要实现大量的 getter 和 setter,这非常烦人。

我尝试的一个解决方案是提供一个简单的基础 class,如 MsgBase,并定义所有公共属性以及 getter 和 setter。对于 Msg 的每个具体类型,我都嵌入了一个指向 MsgBase 的指针。该解决方案有效。

但是,我想在具体的 Msg 类型中嵌入 MsgBase 的值版本。这是因为这样的 Msg 在执行过程中创建了太多次,动态分配 MsgBase 会增加垃圾收集开销。我真的希望所有 Msg 都是静态分配的,因为它们由组件传递并且永远不应该共享。如果我使用 MsgBase 的值版本,我将无法使用 MsgBase 中定义的 setter。

请问有没有什么简单的方法可以解决这个问题?

编辑:添加示例代码


type Msg interface {
    // Agent is another interface
    Src() Agent
    SetSrc(a Agent)
    Dst() Agent
    SetDst(a Agent)

    ... // A large number of properties
}

type MsgBase struct {
    src, dst Agent
    ... // Properties as private fields.
}

func (m MsgBase) Src() Agent {
    return m.src
}

func (m *MsgBase) SetSrc(a Agent) {
    m.src = a
}

... // Many other setters and getters for MsgBase


type SampleMsg struct {
    MsgBase // option1
    *MsgBase // option2
}

请记住,Go 没有 object-oriented 与 Java 相同的继承方式。这听起来像是您正在尝试编写一个抽象基础 class 来封装 "message" 的所有部分;这不是典型的 Go 风格。

您描述的字段是典型的消息元数据。您可以将此元数据封装在 pure-data 结构中。它不一定需要任何行为,也不一定需要 getter 和 setter 方法。

type MessageMeta struct {
  Source Agent
  Destination Agent
}

更 object-oriented 的方法是说一条消息有一个(可变的)元数据块和一个(不可变的,编码的)有效负载。

import "encoding"

type Message interface {
  encoding.BinaryMarshaler // requires MarshalBinary()
  Meta() *MessageMeta
}

type SomeMessage struct {
  MessageMeta
  Greeting string
}

func (m *SomeMessage) Meta() *MessageMeta {
  return &m.MessageMeta
}

func (m *SomeMessage) MarshalBinary() ([]byte, error) {
  return []byte(m.Greeting), nil
}

将这两件事分开传递的更程序化的方法也是合理的。在这种情况下,没有什么是 "message" 的接口,您只需传递编码的有效负载; standard-library 接口,如 encoding.BinaryMarshaler 在这里可能有意义。您可以将其包含在属于您的库的 lower-level 界面中。

func Deliver(meta *MessageMeta, payload []byte) error { ... }

将一种翻译成另一种很容易

func DeliverMessage(m Message) error {
  payload, err := m.Payload()
  if err != nil {
    return err
  }
  meta := m.Meta()
  return Deliver(meta, payload)
}

如果其中一个元数据字段是 "delivered at",请确保通过链一直传递指向元数据对象的指针,这样您就可以更新原始对象中的该字段。

我不会担心垃圾收集作为 first-class 考虑因素,除非是为了避免过度浪费,以及如果 GC 开始出现在配置文件中则检查对象分配。创建两个对象而不是一个对象在这里可能不会成为一个大问题。