在Go语言中使用面向对象的结构体对象

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

OO style struct objects in Go

问题

我对Go语言非常陌生,我从FAQ中了解到Go既是面向对象的,也不是面向对象的。我想使用结构体来创建数据结构,并试图将结构体视为简单的对象。我构建一个汽车作为我的概念验证。我知道汽车是现实世界的对象,所以它适合面向对象编程,但在Go中可能有些奇怪。但是我想象一个用户类也同样方便,所以这对我来说是一个有用的学习练习和参考。

这个示例可以编译,但不能正常工作。它使用了多个源文件,所以你需要操作你的GOPATH并创建一个项目文件夹。

目录结构应该是这样的:

$GOPATH/src/car/car.go
$GOPATH/src/car/parts/engine.go

或者另一种方式:

$ cd /tmp/go/src
$ tree 
.
└── car
    ├── car.go
    └── parts
        └── engine.go

在下面的代码中,main函数要求car实例调用.Start()方法。但当它回到main函数时,car并没有启动。

/* car/car.go */
package main

import (
    "car/parts"
    "fmt"
)

type Car struct {
    sMake  string
    model  string
    engine parts.Engine
}

func init() { // 可选的包初始化函数
    // 注意我们不能将其用作构造函数
}

func main() {
    car := Car{
        sMake: "AMC",
        model: "Gremlin",
    }
    fmt.Printf("I'm going to work now in my %s %s\n", car.sMake, car.model)

    fmt.Println("I guess I should start my car.")
    car.Start()
    fmt.Println("Engine started?", car.engine.IsStarted())
    // 失败--引擎未启动 :(
}

func (car Car) Start() {
    fmt.Println("starting engine ...")
    car.engine.Start()
    fmt.Println("you'd think it would be started here ...", car.engine)
    // 但它没有启动
}

将源文件拆分成多个文件很方便。以下是所有的代码:

/* car/parts/engine.go */
package parts

import (
    "fmt"
)

type Engine struct {
    cylinders int
    started   bool
}

func (engine Engine) Start() {
    fmt.Println("Inside the Start() func, started starts off", engine.started)
    engine.started = true
    fmt.Println("Inside the Start() func, then turns to", engine.started)
    // 这是一个健全性检查
}

func (engine Engine) IsStarted() bool {
    return engine.started
}

运行代码输出如下:

$ go run car.go

> I'm going to work now in my AMC Gremlin
> I guess I should start my car.
> starting engine ...
> Inside the Start() func, started starts off false
> Inside the Start() func, then turns to true
> you'd think it would be started here ... {0 true}
> Engine started? false

调用结构体上的函数是有意义的,但我想知道我是否以错误的方式操作了内部状态?或者我可能不理解作用域。如果有人能帮助我解决这个问题,我将非常感激,以供参考。

另外,如果有人对初始化器有更好或更典型的方法,请告诉我。例如,引擎可能默认为4个汽缸。

英文:

I'm very new to Go and I've read (from the FAQ) that Go is both OO and not. I'd like to create data structures using Structs and find myself trying to think of Structs as simple objects. My canonical proof of concept is building a Car. I understand that a car is a real world object so it lends itself to OOP, which might be weird in Go. But I imagine that a User class would be equally convenient so this will be a useful learning exercise and reference for me.

This example compiles but does not function properly. It uses multiple source files so you'll have to manipulate your GOPATH and create a project folder for this.

It should look like this:

$GOPATH/src/car/car.go
$GOPATH/src/car/parts/engine.go

Or another way to look at it:

$ cd /tmp/go/src
$ tree 
.
└── car
    ├── car.go
    └── parts
        └── engine.go

Main asks the car instance to .Start() below. When it comes back to main, car is not started.

/* car/car.go */
package main

import (
	"car/parts"
	"fmt"
)

type Car struct {
	sMake  string
	model  string
	engine parts.Engine
}

func init() { // optional init of package
	// note that we can't use this as a constructor?
}

func main() {
	car := Car{
		sMake: "AMC",
		model: "Gremlin",
	}
	fmt.Printf("I'm going to work now in my %s %s\n", car.sMake, car.model)

	fmt.Println("I guess I should start my car.")
	car.Start()
	fmt.Println("Engine started?", car.engine.IsStarted())
	// fail -- engine started is false  :(
}

func (car Car) Start() {
	fmt.Println("starting engine ...")
	car.engine.Start()
	fmt.Println("you'd think it would be started here ...", car.engine)
	// but it's not
}

Splitting up the source files is convenient. All of this works

/* car/parts/engine.go */
package parts

import (
	"fmt"
)

type Engine struct {
	cylinders int
	started   bool
}

func (engine Engine) Start() {
	fmt.Println("Inside the Start() func, started starts off", engine.started)
	engine.started = true
	fmt.Println("Inside the Start() func, then turns to", engine.started)
	// this is a sanity check
}

func (engine Engine) IsStarted() bool {
	return engine.started
}

Running this outputs:

$ go run car.go

> I'm going to work now in my AMC Gremlin
> I guess I should start my car.
> starting engine ...
> Inside the Start() func, started starts off false
> Inside the Start() func, then turns to true
> you'd think it would be started here ... {0 true}
> Engine started? false

Calling functions on the structs makes sense but I wonder if I'm trying to manipulate internal state in the wrong way? Or maybe I don't understand scopes. If someone could help me through this, I'd value it greatly for reference.

Also if someone has a preferred or idiomatic method for initializers. For example, the engine might default to 4 cylinders.

答案1

得分: 5

方法

指针 vs. 值

关于接收器的指针和值的规则是,值方法可以在指针和值上调用,但指针方法只能在指针上调用。这是因为指针方法可以修改接收器;在值的副本上调用它们会导致这些修改被丢弃。

因此,为了使您的 EngineStart 方法工作,请使用指针接收器,因为该方法修改了接收器。例如,

package main

import (
	"fmt"
)

type Engine struct {
	cylinders int
	started   bool
}

func (engine *Engine) Start() {
	fmt.Println("在 Start() 函数内部,started 开始为", engine.started)
	engine.started = true
	fmt.Println("在 Start() 函数内部,然后变为", engine.started)
	// 这是一个健全性检查
}

func (engine *Engine) IsStarted() bool {
	return engine.started
}

func main() {
	var engine Engine
	fmt.Println(engine.IsStarted())
	engine.Start()
	fmt.Println(engine.IsStarted())
}

输出:

false
在 Start() 函数内部,started 开始为 false
在 Start() 函数内部,然后变为 true
true
英文:

> Methods
>
> Pointers vs. Values
>
> The rule about pointers vs. values for receivers is that value methods
> can be invoked on pointers and values, but pointer methods can only be
> invoked on pointers. This is because pointer methods can modify the
> receiver; invoking them on a copy of the value would cause those
> modifications to be discarded.

Therefore, for your Engine Start method to work, use a pointer receiver, since the method modifies the receiver. For example,

package main

import (
	"fmt"
)

type Engine struct {
	cylinders int
	started   bool
}

func (engine *Engine) Start() {
	fmt.Println("Inside the Start() func, started starts off", engine.started)
	engine.started = true
	fmt.Println("Inside the Start() func, then turns to", engine.started)
	// this is a sanity check
}

func (engine *Engine) IsStarted() bool {
	return engine.started
}

func main() {
	var engine Engine
	fmt.Println(engine.IsStarted())
	engine.Start()
	fmt.Println(engine.IsStarted())
}

Output:

false
Inside the Start() func, started starts off false
Inside the Start() func, then turns to true
true

答案2

得分: 2

你正在通过值传递接收器。改为通过指针传递:

英文:

You're passing the receiver by value. Pass by pointer instead:

func (engine *Engine) Start() {
             ^
}

huangapple
  • 本文由 发表于 2013年6月16日 00:04:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/17125497.html
匿名

发表评论

匿名网友

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

确定