Golang:代码重复和相似结构

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

Golang: code duplication and similar structs

问题

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

import "time"

type Utmp interface {
    User() string
    Time() time.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]byte
    ut_name [16]byte
    ut_host [16]byte
    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)
}

通过定义一个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:

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)
}

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):

type Utmp interface {
    Time() time.Time
}

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

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

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

package test

import "time"

type Utmp struct {
    // 公共字段
}

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

type LinuxUtmp struct {
    Utmp
    // Linux 特定字段
}

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

type BsdUtmp struct {
    Utmp
    // BSD 特定字段
}

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

任何导入该库的代码都可以直接在 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 -->

package test

import &quot;time&quot;

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)
}

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:

确定