避免在 Go 中编写过多的 getter 和 setter
Avoid writing too many getters and setters in Go
我正在用 Go 实现一个消息传递系统。所以我有一个通用接口,叫做 Msg
。 Msg
接口定义了许多公共字段,例如源、目标、发送时间、接收时间等。我无法定义 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 开始出现在配置文件中则检查对象分配。创建两个对象而不是一个对象在这里可能不会成为一个大问题。
我正在用 Go 实现一个消息传递系统。所以我有一个通用接口,叫做 Msg
。 Msg
接口定义了许多公共字段,例如源、目标、发送时间、接收时间等。我无法定义 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 开始出现在配置文件中则检查对象分配。创建两个对象而不是一个对象在这里可能不会成为一个大问题。