为了最小化代码重复,Go的惯用方式是父类(super class)相似(但不相同)的数据类型吗?字幕示例:

import "time"

type LinuxUtmp struct {
    ut_type uint16
    _       [2]byte
    ut_pid  uint32
    ut_line [32]byte
    ut_id   [4]byte
    ut_user [32]byte
    ut_host [256]byte
    exit_status [2]uint32
    tv_sec  uint32
    tv_usec uint32
    ...
}

func (l LinuxUtmp) User() string {
    return string(l.ut_user[:])
}

func (l LinuxUtmp) Time() time.Time {
    return time.Unix(int64(l.tv_sec), int64(l.tv_usec))
}

type BsdUtmp struct {
    ut_line [8]char
    ut_name [16]char
    ut_host [16]char
    ut_time uint32
}

func (b BsdUtmp) User() string {
    return string(b.ut_user[:])
}

func (b BsdUtmp) Time() time.Time {
    return time.Unix(int64(b.ut_time), 0)
}

显然,这还不止于此,但我希望能够以某种方式对它们进行父类(super class),因此我只需要编写和维护一个特定功能的副本即可。接口(interface)似乎是“正确”的方式,但是有很多不足之处(无效示例):
type Utmp interface {
    Time() time.Time
}

func User(u Utmp) string {
    return string(u.ut_user[:])
}

我也考虑过嵌入,但是由于Go的类型非常严格,所以这似乎也是死路一条。我是否注定要有多个代码,除了签名外,其他所有代码都相同?

[编辑]

复杂的部分原因是我正在使用encoding/binary.Read()根据字节顺序分析此数据(不只是utmp记录,而不仅仅是Linux/BSD)。要使用该字段,必须按照它们在磁盘上的确切顺序将这些字段[导出]到结构中。因此,我不能只嵌入另一个结构的字段,因为在某些记录中它们的顺序不同(大小不同)

最佳答案

我不理解您对嵌入的评论。这是我的方法(使用嵌入):

package test

import "time"

type Utmp struct {
    // Common fields
}

func (u Utmp) User() {
    return string(l.ut_user[:])
}

type LinuxUtmp struct {
    Utmp
    // Linux specific fields
}

func (l LinuxUtmp) Time() time.Time {
    return time.Unix(int64(l.tv_sec), int64(l.tv_usec))
}

type BsdUtmp struct {
    Utmp
    // BSD specific fields
}

func (b BsdUtmp) Time() time.Time {
    return time.Unix(int64(b.ut_time), 0)
}

任何导入该库的代码都可以直接在User()LinuxUtmp对象上以BsdUtmpl.User()的形式直接调用b.User()方法,而根本不提及Utmp。如果愿意,您甚至可以使Utmp保持意外状态(如utmp)。

有关详细信息,请查看Effective Go

如果愿意,您甚至可以确保仅将用于相关平台的代码编译为二进制文件。 This blog有一些示例。为了使事情保持简单,如果平台特定的代码不是很大或涉及其他因素,我将不走这条路。

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

10-06 04:09