英文:
Accessing a generic struct inside another struct
问题
我有一些结构体,
type Fruit struct {
Name string
Sweetness int
}
type Meat struct {
Name string
Bloodiness int
}
有时候一个人可能吃水果,有时候吃肉。
所以我们有另一个结构体。
type Person struct {
Name string
Mealtype interface{}
}
这个Mealtype的interface{}部分是我为了解决问题而编写的。
Go允许我将Mealtype设置为Meat或Fruit结构体,但是我似乎无法访问结构体的任何内部数据。
fmt.Println(someperson.Mealtype)不能让我访问.Bloodiness或.Sweetness。
例如,如果我这样做:
f := Fruit{}
f.Name = "Orange"
f.Sweetness = 10
p := Person{}
p.Name = "John"
p.Mealtype = f
fmt.Println(p.Mealtype.Name)
我会得到错误:
p.Mealtype.Name undefined (type interface{} has no field or method Name)
英文:
I have a some structs,
type Fruit struct {
Name string
Sweetness int
}
type Meat struct {
Name string
Bloodiness int
}
Somtimes a person may eat some fruit, sometimes some meat.
So we have another struct.
type Person struct {
Name string
Mealtype interface{}
}
It's this 'Mealtype' interface{} bit I kind of made up to fix my issue.
Go is allowing me to set either a Mealtype to be a Meat or Fruit struct, however. I can't seem to access any of the internal data from the struct.
The fmt.Println( someperson.Mealtype ) doesn't offer my to access either .Bloodiness or .Sweetness
For example, if i do:
f := Fruit{}
f.Name = "Orange"
f.Sweetness = 10
p := Person{}
p.Name = "John"
p.Mealtype = f
fmt.Println(p.Mealtype.Name)
I get the error:
p.Mealtype.Name undefined (type interface{} has no field or method Name)
答案1
得分: 1
如果你希望Fruit和Meat共享一个可以访问的Name值,你可能想要创建一个接口,让它们两个都实现。例如:
type Food interface {
Name() string
}
func (f Fruit) Name() string {
return f.name
}
func (m Meat) Name() string {
return m.name
}
type Person struct {
Name string
Mealtype Food
}
(注意,Meat和Fruit的字段name现在是小写,以避免与Name冲突)
然后你就可以调用fmt.Println(p.Mealtype.Name())。
为了完整起见,你也可以使用类型断言,但在你的例子中可能不是你想要做的。但它可能会像这样:
if fruit, ok := p.MealType.(Fruit); ok {
fmt.Println(fruit.Name)
}
英文:
If you want both Fruit and Meat to share a Name value that can be accessed, you probably want to create an interface that the two of them implement. For example
type Food interface {
Name() string
}
func (f Fruit) Name() string {
return f.name
}
func (m Meat) Name() string {
return m.name
}
type Person struct {
Name string
Mealtype Food
}
(note that the Meat and Fruit's field name is now lowercase, to avoid conflict with Name)
Then you should be able to call fmt.Println(p.Mealtype.Name()).
For completeness, you can also use type assertion, but that's probably not what you want to do in your example. But it would look something like this:
if fruit, ok := p.MealType.(Fruit); ok {
fmt.Println(fruit.Name)
}
答案2
得分: 0
你应该在使用Mealtype之前描述Mealtype接口,例如:
type MealtypeInterface interface {
Name() string
}
然后在Person类型中,将Mealtype定义为MealtypeInterface,而不是interface{}:
type Person struct {
Name string
Mealtype MealtypeInterface
}
英文:
You should describe Mealtype interface before using it. Like
type MealtypeInterface interface {
Name() string
}
Then in type Person you define Mealtype as MealtypeInterface, not interface{}
type Person struct {
Name string
Mealtype MealtypeInterface
}
答案3
得分: 0
接口定义了一种与不一致类型的变量进行交互的一致方式。因此,你需要找到一种方法来使得访问肉类的“血腥度”和水果的“甜度”具有一致性。
以下是一种使得“甜度”和“血腥度”在一致接口后面的方法:
interface Edible {
PrimaryAttribute() (string, int)
}
func (f Fruit)PrimaryAttribute() (string, int) {
return "Sweetness", f.Sweetness
}
// 当然,对于肉类,返回血腥度 m.Bloodiness
对于习惯于解释型语言(如Python或JavaScript等)的程序员来说,将结构字段名称视为运行时可访问的数据是很常见的。但在Go语言中,这只有在使用reflect时才成立,我不鼓励新的Go程序员使用它。你可以思考一下,是否更愿意定义一种一致的方法来描述可食用物品的任意命名属性:
type Edible struct {
FlavorAttributes map[string]int
}
fish_meat := &Edible{FlavorAttributes: map[string]int{"Bloodiness": 3}}
现在,“血腥度”和“甜度”不再是字段,它们是映射中的字符串,在运行时更容易访问。
在编程书籍中,通常会以人类的层次结构组织(类Animal,类Dog,类Labrador的层次结构)来讨论多态性。阅读《设计模式:可复用面向对象软件的基础》可以了解为什么很多软件最终没有按照人类的角度实现类层次结构的一些好思路。
英文:
an interface defines a consistent way of interacting with variables of inconsistent types. So you have to find a way to make accessing Bloodiness of meats and Sweetness of fruits consistently.
Here's one way that Sweetness and Bloodiness could be behind a consistent interface:
interface Edible {
PrimaryAttribute() (string, int)
}
func (f Fruit)PrimaryAttribute() (string, int) {
return "Sweetness", f.Sweetness
}
// and return Bloodiness, m.Bloodiness for meats of course
Its common for programmers used to interpreted languages like python or javascript, etc, to think of struct field names as runtime-accessible data. But this is only the case in Go with reflect, which I try to discourage new Go programmers to use. Ask yourself if you wouldn't rather define a consistent method of describing arbitrary named attributes of your edibles:
type Edible struct {
FlavorAttributes map[string]int
}
fish_meat := &Edible{FlavorAttributes: map[string]int{"Bloodiness": 3}
Now Bloodiness and Sweetness are not fields, they are strings in a map, and more accessible at runtime.
It's common for programming books to talk about polymorphism in terms of human hierarchical organization (class Animal, class Dog, class Labrador hierarchy). Read "Design Patterns: Elements of Reusable Object-Oriented Software" for some good thoughts about why a lot of software doesn't end up implementing class hierarchies from the human perspective.
答案4
得分: 0
如果你想要在Go 1.18上使用泛型,可以这样做:
package main
import "fmt"
type Fruit struct {
Name string
Sweetness int
}
type Meat struct {
Name string
Bloodiness int
}
type Mealtype interface {
Fruit | Meat
}
type Person[M Mealtype] struct {
Name string
Meal M
}
func main() {
f := Fruit{}
f.Name = "Orange"
f.Sweetness = 10
p := Person[Fruit]{}
p.Name = "John"
p.Meal = f
fmt.Println(p.Meal.Name)
fmt.Println(p.Meal.Sweetness)
fmt.Println()
m := Meat{"Chicken", 10}
/* 错误
p = Person[Meat]{}
p.Name = "John"
p.Meal = m
fmt.Println(p.Meal.Name)
fmt.Println(p.Meal.Bloodiness)
*/
p2 := Person[Meat]{}
p2.Name = "John"
p2.Meal = m
fmt.Println(p2.Meal.Name)
fmt.Println(p2.Meal.Bloodiness)
}
你应该重新声明 Person。
英文:
If you want generics that are on go 1.18, this is it
package main
import "fmt"
type Fruit struct {
Name string
Sweetness int
}
type Meat struct {
Name string
Bloodiness int
}
type Mealtype interface {
Fruit | Meat
}
type Person[M Mealtype] struct {
Name string
Meal M
}
func main() {
f := Fruit{}
f.Name = "Orange"
f.Sweetness = 10
p := Person[Fruit]{}
p.Name = "John"
p.Meal = f
fmt.Println(p.Meal.Name)
fmt.Println(p.Meal.Sweetness)
fmt.Println()
m := Meat{"Chicken", 10}
/* error
p = Person[Meat]{}
p.Name = "John"
p.Meal = m
fmt.Println(p.Meal.Name)
fmt.Println(p.Meal.Bloodiness)
*/
p2 := Person[Meat]{}
p2.Name = "John"
p2.Meal = m
fmt.Println(p2.Meal.Name)
fmt.Println(p2.Meal.Bloodiness)
}
You should re declare Person
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论