如何设计具有可修改字段的结构?

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

How to design structure with modifiable fields?

问题

我正在使用Go语言编写一个简单的游戏,并遇到了一些问题。我的代码如下所示:

package main

import "fmt"

type Location struct {
	X int
	Y int
}

type Car struct {
	MaxSpeed int
	Loc      Location
}

func (car Car) SetLocation(loc Location) {
	car.Loc = loc
}

func (car Car) GetLocation() Location {
	return car.Loc
}

type Bike struct {
	GearsNum int
	Loc      Location
}

func (bike Bike) SetLocation(loc Location) {
	bike.Loc = loc
}

func (bike Bike) GetLocation() Location {
	return bike.Loc
}

type Movable interface {
	GetLocation() Location
	SetLocation(Location)
}

type Fleet struct {
	vehicles []Movable
}

func (fleet *Fleet) AddVehicles(v ...Movable) {
	for _, x := range v {
		fleet.vehicles = append(fleet.vehicles, x)
	}
}

func (fleet *Fleet) WherTheyAre() {
	for _, v := range fleet.vehicles {
		fmt.Println(v.GetLocation())
	}
}

func main() {
	myCar := Car{MaxSpeed: 200, Loc: Location{12, 34}}
	myBike := Bike{GearsNum: 11, Loc: Location{1, 1}}

	myFleet := Fleet{}
	myFleet.AddVehicles(myCar)
	myFleet.AddVehicles(myBike)
	myFleet.WherTheyAre()

	myCar.SetLocation(Location{0, 0})
	myFleet.WherTheyAre()
}

假设Car和Bike是非常大的结构体,我不想复制它们。我应该如何设计代码,以便能够修改Fleet中的车辆的位置?换句话说,如何设计Fleet结构体以便能够修改其可移动对象?

我尝试过使用指向接口的指针进行实验,但这不是一个好主意...

谢谢帮助!

英文:

I am writing a simple game in Go and have some problems with it. My, cut off, code looks like this:

package main
import "fmt"
type Location struct {
X int
Y int
}
type Car struct {
MaxSpeed int
Loc Location
}
func (car Car) SetLocation(loc Location) {
car.Loc = loc
}
func (car Car) GetLocation() Location {
return car.Loc
}
type Bike struct {
GearsNum int
Loc Location
}
func (bike Bike) SetLocation(loc Location) {
bike.Loc = loc
}
func (bike Bike) GetLocation() Location {
return bike.Loc
}
type Movable interface {
GetLocation() Location
SetLocation(Location)
}
type Fleet struct {
vehicles []Movable
}
func (fleet *Fleet) AddVehicles(v ...Movable) {
for _, x := range(v) {
fleet.vehicles = append(fleet.vehicles, x) 
}
}
func (fleet *Fleet) WherTheyAre() {
for _, v := range(fleet.vehicles) {
fmt.Println(v.GetLocation())
}
}
func main() {
myCar := Car{MaxSpeed: 200, Loc: Location{12, 34}}
myBike := Bike{GearsNum: 11, Loc: Location{1, 1}}
myFleet := Fleet{}
myFleet.AddVehicles(myCar)
myFleet.AddVehicles(myBike)
myFleet.WherTheyAre()
myCar.SetLocation(Location{0,0})
myFleet.WherTheyAre()
}

The assumption is that Car and Bike are very big structures which I do not want to copy. How should I design the code to be able to modify the location of the car which is the part of the Fleet? Other words how to design the Fleet struct to be able to modify its movable objects?
I have tried to experiment with pointers to interfaces but I was not good idea...

Thanks for help!

答案1

得分: 1

问题在于你使用了值接收器定义了方法。当你在接收器上调用方法时,接收类型实际上作为参数传递,这种情况下是按值传递,并且你正在修改该副本。

func (bike Bike) SetLocation(loc Location) {
    bike.Loc = loc // 修改了 Bike 的本地副本
}

func (bike *Bike) SetLocation(loc Location) {
    bike.Loc = loc // 修改了引用的 bike
}

不过你必须以不同的方式声明你的类型,或者在调用这些方法时使用 & 运算符,因为你的主函数中使用的是值类型。在几乎所有情况下,我更喜欢使用这种语法 bike := &Bike{}。只有在初始化时有方便的方法或者使用值类型的特定原因时,我才会离开这种方式(这种情况非常罕见)。

但基本上,你不能让一个 setter 使用值类型接收器。如果你想将这些结构体作为值类型使用,我建议导出字段,这样就可以在不使用 getter 或 setter 的情况下访问它们。另外,就风格而言,除非你真的在抽象一些逻辑,否则我不希望看到使用 getter 和 setter。如果你提供一个直接将传入的值分配给字段的 setter,那么不导出字段是没有意义的。另外,我没有仔细看,但你导出了结构体的所有字段,所以你的 setter 是无用的,大多数程序员只会这样做:bike.Loc = anythingIWantBecauseThisFieldsExportedWhichYouMayThinkOfAsPublic

英文:

The problem is that you've defined the method with value receivers. When you call a method on a receiver the receiving type is actually being passed as an argument, in this case by value and you're modifying that copy.

func (bike Bike) SetLocation(loc Location) {
bike.Loc = loc // modifies local copy of Bike
}
func (bike *Bike) SetLocation(loc Location) {
bike.Loc = loc // modifies the referenced bike
}

You gotta declare your types differently though, or use the & operator in order to call these methods because you have value types in your main. My preference is to use this syntax bike := &Bike{} in almost all cases. I will only go away from it if I have a convenience method for initilization or a very specific reason for using a value type (which is extremely rare).

But basically, you can't make a setter use a value type receiver. If you want to use these structs as value types I would recommend exporting the fields so they can be accessed without getter or setter. Also, just regarding style, I would be displeased to see the use of getters and setters at all unless you actually are abstracting some logic. There's no point in not exporting a field if you're going to provide a setter that directly assigns the value passed in to said field. Also, wasn't looking to closely but you are exporting all fields on your structs so your setters are useless and most programmers would just do bike.Loc = anythingIWantBecauseThisFieldsExportedWhichYouMayThinkOfAsPublic

huangapple
  • 本文由 发表于 2015年6月12日 05:56:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/30792131.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定