英文:
Golang interface and common fields
问题
我是你的中文翻译助手,以下是你要翻译的内容:
我是一个新手,在使用Go语言和接口时遇到了一个架构问题:
我需要存储有关车辆的信息,假设车辆可以是汽车、公交车、救护车、消防车等等。所以车辆是一个接口,每种具体的车辆类型都是一个结构体,但它们都有一些共同的字段(颜色、轮子数量、座位数、消声器等等),当我需要获取一些共同的信息时,可以采取以下几种方式:
- 完全不使用接口,只使用一个大的结构体,其中包含很多的getter和setter方法,每次都要检查“车辆类型X是否可以有字段Z”。这样会使得这些方法很难阅读,也很容易忘记检查某些东西。
- 每次都进行类型断言来获取具体的字段。所以,为了获取颜色,我需要写一个10行的类型切换。
- 为每个共同的字段添加getter方法:
type Vehicle interface {
Colour() string
Wheels() int
Seats() int
Mufflers() int
...
}
从我看来,这是一个“保持接口小”的反模式,并且会产生很多非常相似的代码(每个结构体都有相同的方法)。
- 创建一个类似CommonVehicle的结构体,用于存储所有共同的字段,然后将其他车辆类型嵌入其中,接口只有一个方法返回这个CommonVehicle:
type Vehicle interface {
Common() CommonVehicle
}
type CommonVehicle struct {
// 共同的字段
}
type Car struct {
CommonVehicle
// 不常见的字段
}
// 实现Vehicle接口的方法
当我需要获取颜色时,我可以使用vehicle.Common().Colour。这在接口和类型方面看起来很清晰,但每次都调用Common来获取车辆的任何信息可能会产生误导。
对于这个问题,有什么最佳实践吗?也许我漏掉了什么,需要采取其他方式吗?
英文:
I kinda newbee and have an architecture problem on go & interface:
I need to store info about Vehicle and assume it could be Car, Bus, Ambulance, Firetruck etc. So Vehicle is interface and every exact Vehicle type is struct, but all of them have some common fields (colour, number of wheels, seats, mufflers etc) and when I need to get something common I can go following ways:
- Don't use interface at all and have one big struct with a lot of getters and setters checked every time "if vehicle type X can have field Z?". It makes those methods very hard to read and very easy to forget to check smthng.
- Perform type assert every time on every type to get exact field. So just to get colour I will need to write type switch for 10 lines.
- Add to interface getter for every common field:
type Vehicle interface {
Colour() string
Wheels() int
Seats() int
Mufflers() int
...
}
As I see it's anti-pattern for "keep interface small" and will produce a lot of very similiar code (same method for every struct)
- Have some struct like CommonVehicle which store all common fields and all other vehicle type embed it and interface have only method return this CommonVehicle:
type Vehicle interface {
Common() CommonVehicle
}
type CommonVehicle struct {
// common fields
}
type Car struct {
CommonVehicle
// uncommon fields
}
// implementation for Vehicle interface
When I need to get colour I will do vehicle.Common().Colour. It looks clear on interface and types side, but it could mislead to call every time Common to get anything from Vehicle.
What is the best practice for this? Maybe I missed something and need to go some other way?
答案1
得分: 0
你正在尝试使用Go语言中不支持的继承(Inheritance)机制(有些人认为这种模式整体上并不好)。相反,你应该使用组合(Composition)机制(这正是你第4个选项所建议的)。
这篇博文对此进行了完美的描述。
作为一般设计规则的另一种方法
与其试图定义对象的接口,你应该着眼于你希望对象能够执行的功能。
这与你的问题并不完全相关,但它有助于保持小接口模式的一致性。
让我们以车辆为例:我们可以用车辆做什么?
- 驾驶它
- 可能听音乐
- 出售它
- 对它的美感到惊叹
现在我们知道我们可以用车辆做什么了,我们应该将每个动作作为独立的接口。
为什么呢?因为这样可以对任何车辆实体进行最细粒度的描述,而不对车辆的具体实现做任何假设。
所以我们有以下接口:
type Drivable interface{ Drive(float64, float64) } // 驾驶到某个纬度、经度坐标
type Listenable interface{ Listen() []byte } // Listen 返回一串字节流作为音频输出
type Sellable interface { Sell(float64, string) } // 以X金额卖给某个人
type Describable interface { Describe() string }
现在有了这4个接口,我们可以创建任何类型的车辆:
- 有些车辆可能没有收音机,所以它们可能不应该实现
Listenable
接口。 - 有些车辆是警车-城市拥有它们-所以它们实际上是不可出售的。
- 等等...
关键是拥有不同的功能,并从这些功能中构建我们的实体-而不是相反。
英文:
You are trying to have Inheritance which is not supported in Go (and some believe it is not a good pattern overall). Instead, you should use Composition (which is exactly what your #4 option suggests).
This blog post describes it perfectly
A different approach as a general design rule
instead of trying to define interfaces of objects, you should aim towards capabilities you wish your objects will be able to do.
This does not correlate 100% with your question but it will help with maintaining the pattern of small interfaces.
Let's take a vehicle as an example: what can we do with a vehicle?
- drive it
- perhaps listen to music
- sell it
- be amazed of its beauty
Now that we know what we can do with a vehicle, we should have each of these actions as its own interface.
Why? - because it allows for the most granular description of any vehicle entity, without any assumptions about what specific implementations of a vehicle have.
So we have the following interfaces:
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 }
Now with these 4 interfaces, we can create any type of vehicle we want:
- Some vehicles won't have a radio, so they probably shouldn't implement the
Listenable
interface. - Some vehicles are police vehicles - the city owns them - so they are not really sellable
- etc, etc...
The point is to have different capabilities and from those capabilities we should build our entities - and not vice-versa.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论