Golang:代码重复和相似结构

huangapple go评论126阅读模式
英文:

Golang: code duplication and similar structs

问题

在Go语言中,可以使用接口来实现类似但不完全相同的数据类型的上层类型,以减少代码重复。以下是一个简单的示例:

  1. import "time"
  2. type Utmp interface {
  3. User() string
  4. Time() time.Time
  5. }
  6. type LinuxUtmp struct {
  7. ut_type uint16
  8. _ [2]byte
  9. ut_pid uint32
  10. ut_line [32]byte
  11. ut_id [4]byte
  12. ut_user [32]byte
  13. ut_host [256]byte
  14. exit_status [2]uint32
  15. tv_sec uint32
  16. tv_usec uint32
  17. // ...
  18. }
  19. func (l LinuxUtmp) User() string {
  20. return string(l.ut_user[:])
  21. }
  22. func (l LinuxUtmp) Time() time.Time {
  23. return time.Unix(int64(l.tv_sec), int64(l.tv_usec))
  24. }
  25. type BsdUtmp struct {
  26. ut_line [8]byte
  27. ut_name [16]byte
  28. ut_host [16]byte
  29. ut_time uint32
  30. }
  31. func (b BsdUtmp) User() string {
  32. return string(b.ut_user[:])
  33. }
  34. func (b BsdUtmp) Time() time.Time {
  35. return time.Unix(int64(b.ut_time), 0)
  36. }

通过定义一个Utmp接口,你可以将LinuxUtmpBsdUtmp类型都作为Utmp类型的实现。这样,你就可以使用Utmp类型的变量来调用User()Time()方法,而无需关心具体的实现类型。

请注意,这只是一个简单的示例,实际情况可能更加复杂。你可能需要根据具体的需求来定义接口和方法,并在实现类型中提供相应的功能。

英文:

What's an idiomatic way in Go to superclass similar (but not identical) data types in order to minimize code duplication? Trite example:

  1. import "time"
  2. type LinuxUtmp struct {
  3. ut_type uint16
  4. _ [2]byte
  5. ut_pid uint32
  6. ut_line [32]byte
  7. ut_id [4]byte
  8. ut_user [32]byte
  9. ut_host [256]byte
  10. exit_status [2]uint32
  11. tv_sec uint32
  12. tv_usec uint32
  13. ...
  14. }
  15. func (l LinuxUtmp) User() string {
  16. return string(l.ut_user[:])
  17. }
  18. func (l LinuxUtmp) Time() time.Time {
  19. return time.Unix(int64(l.tv_sec), int64(l.tv_usec))
  20. }
  21. type BsdUtmp struct {
  22. ut_line [8]char
  23. ut_name [16]char
  24. ut_host [16]char
  25. ut_time uint32
  26. }
  27. func (b BsdUtmp) User() string {
  28. return string(b.ut_user[:])
  29. }
  30. func (b BsdUtmp) Time() time.Time {
  31. return time.Unix(int64(b.ut_time), 0)
  32. }

Obviously there's more to it than that, but I'd love to be able to somehow superclass those so I only have to write and maintain one copy of particular functions. An interface seems to be the "right" way, but that leaves much to be desired (non-working example):

  1. type Utmp interface {
  2. Time() time.Time
  3. }
  4. func User(u Utmp) string {
  5. return string(u.ut_user[:])
  6. }

I've also considered embedding, but that seems a dead end too since Go is so strictly typed. Am I doomed to have multiple pieces of code that are identical in every way but the signature?

[edit]

Part of the complication is that I'm using encoding/binary.Read() to parse this data according to endianness (it's not just utmp records and not just Linux/BSD). To use that, the fields must be [exported] in the struct in the precise order they are on-disk. Hence I can't just embed the fields of another struct, as in some records they're in different order (and of differing sizes)

答案1

得分: 0

如果你遇到一些事物类型不同的问题,你可以让TimeUser函数操作一个封装了Linux和BSD功能的接口。

如果你不喜欢这样做,你可以生成代码来避免重复。

英文:

If you have the problem that the types of some things are different, you could make the Time and User functions operate on an interface that wraps linux and bsd functionality.

If you don't like that, you can generate the code to avoid duplication.

答案2

得分: 0

我不理解你关于嵌入的评论。以下是我如何做(使用嵌入):

  1. package test
  2. import "time"
  3. type Utmp struct {
  4. // 公共字段
  5. }
  6. func (u Utmp) User() {
  7. return string(l.ut_user[:])
  8. }
  9. type LinuxUtmp struct {
  10. Utmp
  11. // Linux 特定字段
  12. }
  13. func (l LinuxUtmp) Time() time.Time {
  14. return time.Unix(int64(l.tv_sec), int64(l.tv_usec))
  15. }
  16. type BsdUtmp struct {
  17. Utmp
  18. // BSD 特定字段
  19. }
  20. func (b BsdUtmp) Time() time.Time {
  21. return time.Unix(int64(b.ut_time), 0)
  22. }

任何导入该库的代码都可以直接在 LinuxUtmpBsdUtmp 对象上调用 User() 方法,如 l.User()b.User(),而无需提及 Utmp。如果你喜欢,甚至可以将 Utmp 命名为 utmp

详细信息请参阅 Effective Go

如果你愿意,甚至可以确保只有针对相关平台的代码被编译到二进制文件中。这篇博客 提供了一些示例。为了保持简单,如果平台特定的代码不是很大或涉及其他因素,我不会选择这种方法。

为了完整起见,这里是官方的 go build 文档。

英文:

I don't understand your comment about embedding. Here is how I'd do it (using embedding):

<!-- language: lang-golang -->

  1. package test
  2. import &quot;time&quot;
  3. type Utmp struct {
  4. // Common fields
  5. }
  6. func (u Utmp) User() {
  7. return string(l.ut_user[:])
  8. }
  9. type LinuxUtmp struct {
  10. Utmp
  11. // Linux specific fields
  12. }
  13. func (l LinuxUtmp) Time() time.Time {
  14. return time.Unix(int64(l.tv_sec), int64(l.tv_usec))
  15. }
  16. type BsdUtmp struct {
  17. Utmp
  18. // BSD specific fields
  19. }
  20. func (b BsdUtmp) Time() time.Time {
  21. return time.Unix(int64(b.ut_time), 0)
  22. }

Any code importing the library can call User() method directly on LinuxUtmp and BsdUtmp objects directly as l.User() or b.User() without mentioning Utmp at all. You can even keep Utmp unexpected (as utmp) if you like.

Check out Effective Go for details.

You can even ensure that only the code meant for the relevant platform gets compiled in the binary, if you like. This blog has got some examples. In the interest of keeping things simple, I wouldn't bother going down that route if the platform specific code is not very big or there are other factors involved.

For the sake of completeness, here is the official go build doc.

huangapple
  • 本文由 发表于 2015年10月10日 04:59:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/33047091.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定