Golang 接口和公共字段

Golang interface and common fields

我有点新手,在 go 和界面上遇到架构问题: 我需要存储有关车辆的信息,并假设它可能是汽车、公共汽车、救护车、消防车等。所以车辆是接口,每个确切的车辆类型都是结构,但它们都有一些共同的领域(颜色、车轮数量、座位、消声器等),当我需要一些常见的东西时,我可以采用以下方式:

  1. 根本不使用接口,而是有一个包含很多 getter 的大结构,每次检查“车辆类型 X 是否可以有字段 Z?”时设置器。这使得这些方法很难阅读并且很容易忘记检查 smthng。
  2. 每次对每种类型执行类型断言以获得准确的字段。因此,为了获得颜色,我需要编写 10 行的类型开关。
  3. 为每个公共字段添加到界面 getter:
type Vehicle interface {
    Colour() string
    Wheels() int
    Seats() int
    Mufflers() int
    ...
}

正如我所见,它是“保持接口小”的反模式,并且会产生很多非常相似的代码(每个结构都使用相同的方法)

  1. 有一些像 CommonVehicle 这样的结构,它存储所有公共字段,所有其他车辆类型都嵌入它并且接口只有方法 return 这个 CommonVehicle:
type Vehicle interface {
    Common() CommonVehicle
}

type CommonVehicle struct {
   // common fields
}

type Car struct {
   CommonVehicle
   // uncommon fields
}

// implementation for Vehicle interface

当我需要颜色时,我会做 vehicle.Common().Colour。它在接口和类型方面看起来很清楚,但它可能会误导每次调用 Common 以从 Vehicle 获取任何内容。

这方面的最佳做法是什么?也许我错过了什么,需要走其他路?

您正在尝试 Inheritance which is not supported in Go (and some believe it is not a good pattern overall). Instead, you should use Composition(这正是您的 #4 选项所建议的)。

This blog post描述的很完美

作为一般设计规则的不同方法

您不应尝试定义对象的接口,而应着眼于您希望对象能够执行的功能。 这与您的问题并非 100% 相关,但有助于维护小接口的模式。

我们以车辆为例:我们可以用车辆做什么?

  • 开车
  • 也许听听音乐
  • 卖掉它
  • 惊叹于它的美丽

既然我们知道我们可以用载具做什么,我们应该将这些操作中的每一个都作为自己的界面。 为什么? - 因为它允许对任何车辆实体进行最精细的描述,而无需对车辆的具体实现进行任何假设。

所以我们有以下接口:

type Drivable interface{ Drive(float64, float64) } // drive to a latitude, longitude quordinate
type Listenable interface{ Listen() []byte } // Listen return a stream of bytes as the audio output
type Sellable interface { Sell(float64, string) } // sell for X amount of money to some person
type Describable interface { Describe() string }

现在有了这 4 个接口,我们可以创建我们想要的任何类型的车辆:

  • 有些车辆没有收音机,因此它们可能不应该实现 Listenable 接口。
  • 有些车辆是警车 - 市政府拥有它们 - 所以它们实际上卖不出去
  • 等等等等...

重点是拥有不同的功能,我们应该根据这些功能构建我们的实体 - 而不是 vice-versa。