访问一个结构体中的通用结构体

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

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{} 
}

这个Mealtypeinterface{}部分是我为了解决问题而编写的。
Go允许我将Mealtype设置为MeatFruit结构体,但是我似乎无法访问结构体的任何内部数据。
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

如果你希望FruitMeat共享一个可以访问的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

huangapple
  • 本文由 发表于 2022年5月20日 00:02:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/72307721.html
匿名

发表评论

匿名网友

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

确定