Golang 接口和公共字段
Golang interface and common fields
我有点新手,在 go 和界面上遇到架构问题:
我需要存储有关车辆的信息,并假设它可能是汽车、公共汽车、救护车、消防车等。所以车辆是接口,每个确切的车辆类型都是结构,但它们都有一些共同的领域(颜色、车轮数量、座位、消声器等),当我需要一些常见的东西时,我可以采用以下方式:
- 根本不使用接口,而是有一个包含很多 getter 的大结构,每次检查“车辆类型 X 是否可以有字段 Z?”时设置器。这使得这些方法很难阅读并且很容易忘记检查 smthng。
- 每次对每种类型执行类型断言以获得准确的字段。因此,为了获得颜色,我需要编写 10 行的类型开关。
- 为每个公共字段添加到界面 getter:
type Vehicle interface {
Colour() string
Wheels() int
Seats() int
Mufflers() int
...
}
正如我所见,它是“保持接口小”的反模式,并且会产生很多非常相似的代码(每个结构都使用相同的方法)
- 有一些像 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。
我有点新手,在 go 和界面上遇到架构问题: 我需要存储有关车辆的信息,并假设它可能是汽车、公共汽车、救护车、消防车等。所以车辆是接口,每个确切的车辆类型都是结构,但它们都有一些共同的领域(颜色、车轮数量、座位、消声器等),当我需要一些常见的东西时,我可以采用以下方式:
- 根本不使用接口,而是有一个包含很多 getter 的大结构,每次检查“车辆类型 X 是否可以有字段 Z?”时设置器。这使得这些方法很难阅读并且很容易忘记检查 smthng。
- 每次对每种类型执行类型断言以获得准确的字段。因此,为了获得颜色,我需要编写 10 行的类型开关。
- 为每个公共字段添加到界面 getter:
type Vehicle interface {
Colour() string
Wheels() int
Seats() int
Mufflers() int
...
}
正如我所见,它是“保持接口小”的反模式,并且会产生很多非常相似的代码(每个结构都使用相同的方法)
- 有一些像 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。