Go语言 不同类型与泛型的Map

jqjz2hbq  于 2023-08-01  发布在  Go
关注(0)|答案(3)|浏览(142)

如何使用一个通用Map函数(Map2Vehicle)将2种不同的类型(汽车、自行车)Map到一种车辆类型。Car和Bicycle不能更改,因为它是自动生成的代码。

package main

import "fmt"

func main() {
    c := Car{Name: "MyCar", Wheels: CarWheels{Count: 4}}
    b := Bicycle{Name: "MyBicycle", Wheels: BicycleWheels{Count: 2}}
    v1 := Map2Vehicle(&c)
    v2 := Map2Vehicle(&b)
    fmt.Println(v1.Name, v2.Name)
}

// ---------------- Car
type Car struct {
    Name   string
    Wheels CarWheels
}

type CarWheels struct {
    Count int
}

func (c *Car) GetName() string      { return c.Name }
func (c *Car) GetWheels() CarWheels { return c.Wheels }
func (w *CarWheels) GetCount() int  { return w.Count }

// ---------------- Bicycle

type Bicycle struct {
    Name   string
    Wheels BicycleWheels
}

type BicycleWheels struct {
    Count int
}

func (b *Bicycle) GetName() string          { return b.Name }
func (b *Bicycle) GetWheels() BicycleWheels { return b.Wheels }
func (w *BicycleWheels) GetCount() int      { return w.Count }

// ---------------- Vehicle

type wheels interface {
    GetCount() int
}

type vehicle[T wheels] interface {
    GetName() string
    GetWheels() T
}

type Vehicle struct {
    Name string
}

func Map2Vehicle[T vehicle[wheels]](v T) *Vehicle {
    return &Vehicle{
        Name: fmt.Sprintf("%s with %d wheels", v.GetName(), v.GetWheels().GetCount()),
    }
}

字符串

错误:代码不工作:

cmd/tmp/tmp.go:8:19: *Car does not satisfy vehicle[wheels] (wrong type for method GetWheels)
                have GetWheels() CarWheels
                want GetWheels() wheels
cmd/tmp/tmp.go:9:19: *Bicycle does not satisfy vehicle[wheels] (wrong type for method GetWheels)
                have GetWheels() BicycleWheels
                want GetWheels() wheels

预期输出:

MyCar with 4 wheels MyBicycle with 2 wheels


有什么想法可以解决golang泛型的问题吗?

tp5buhyn

tp5buhyn1#

首先:CarWheelsBicycleWheels没有实现wheels接口。这是因为你的GetCount方法使用了指针接收器:

func (w *CarWheels) GetCount() int { return w.Count }
func (w *BicycleWheels) GetCount() int { return w.Count }

字符串
因此,*CarWheels*BicycleWheels(指向结构体的指针)实现了wheels接口,但结构体本身没有。我只是简单地删除指针接收器,因为这些结构非常小(实际上只有int s),并且没有必要使用指针“装箱”它们(这样做只会产生开销):

func (w CarWheels) GetCount() int { return w.Count }
func (w BicycleWheels) GetCount() int { return w.Count }


或者,如果你的真实的结构体更大,你当然也可以让你的GetWheels方法返回 * 指向轮子的指针 * 而不是 * 轮子本身 *。
第二:你的函数有一个签名func Map2Vehicle[T vehicle[wheels]](v T) *Vehicle。这意味着v必须是vehicle,其中GetWheels方法的返回类型为wheels,而不是任何实现wheels的返回类型。这个问题的解决方案是让GetWheels函数返回抽象的wheels类型,而不是特定的类型:

func (c *Car) GetWheels() wheels { return c.Wheels }
func (b *Bicycle) GetWheels() wheels { return b.Wheels }


如果这样做,代码将按预期运行。
或者,如果你想保留特定的类型,你必须让轮子成为函数的第二个泛型参数:

func Map2Vehicle[W wheels, T vehicle[W]](v T) *Vehicle


Go将无法推断W(似乎泛型类型推断不会嵌套),所以现在必须在调用站点明确车轮的类型(但它仍然可以推断车辆的类型):

v1 := Map2Vehicle[CarWheels](&c)
v2 := Map2Vehicle[BicycleWheels](&b)


如果要重构它,我会通过让GetWheels方法返回wheels接口类型来完全消除泛型;那么Map2Vehicle可以在任何vehicle上操作,并且vehicle不需要是泛型的:

type vehicle interface {
    GetName() string
    GetWheels() wheels
}

func Map2Vehicle(v vehicle) *Vehicle {
    return &Vehicle{
        Name: fmt.Sprintf("%s with %d wheels", v.GetName(), v.GetWheels().GetCount()),
    }
}

dly7yett

dly7yett2#

为什么不创建两个不同类型的值,Car和Bicycle,然后使用Map2Vehicle函数将每个值Map到Vehicle类型?
Playground链接:https://play.golang.com/p/lPFoFEsjIeD

package main

import "fmt"

func main() {
    c := Car{Name: "MyCar", Wheels: CarWheels{Count: 4}}
    b := Bicycle{Name: "MyBicycle", Wheels: BicycleWheels{Count: 2}}
    v1 := Map2Car(c)
    v2 := Map2Bicycle(b)
    fmt.Println(v1, v2)
}

// ---------------- Car
type Car struct {
    Name   string
    Wheels CarWheels
}

type CarWheels struct {
    Count int
}

func (c *Car) GetName() string      { return c.Name }
func (c *Car) GetWheels() CarWheels { return c.Wheels }
func (w *CarWheels) GetCount() int  { return w.Count }

// ---------------- Bicycle

type Bicycle struct {
    Name   string
    Wheels BicycleWheels
}

type BicycleWheels struct {
    Count int
}

func (b *Bicycle) GetName() string          { return b.Name }
func (b *Bicycle) GetWheels() BicycleWheels { return b.Wheels }
func (w *BicycleWheels) GetCount() int      { return w.Count }

// ---------------- Vehicle
type Vehicle struct {
    Name string
}

func Map2Car(c Car) *Vehicle {
    return &Vehicle{
        Name: fmt.Sprintf("%s with %d wheels", c.GetName(), c.GetWheels()),
    }
}

func Map2Bicycle(b Bicycle) *Vehicle {
    return &Vehicle{
        Name: fmt.Sprintf("%s with %d wheels", b.GetName(), b.GetWheels()),
    }
}

字符串

输出

nue99wik

nue99wik3#

你试图在Go语言中使用泛型将不同的类型(CarBicycle)Map到一个公共的Vehicle类型。但是,您遇到了一个错误,因为类型CarBicycle对于GetWheels()方法具有不同的实现。
要解决这个问题,您可以修改代码,在CarBicycle结构中为Wheels字段使用接口而不是具体类型。通过这样做,您可以为CarWheelsBicycleWheels定义一个满足wheels接口的GetWheels()实现。下面是代码的更新版本:

package main

import "fmt"

// ------- Car
type Car struct {
    Name   string
    Wheels wheels // Use interface instead of concrete type
}

type CarWheels struct {
    Count int
}

func (c *Car) GetName() string      { return c.Name }
func (c *Car) GetWheels() wheels    { return c.Wheels }
func (w *CarWheels) GetCount() int  { return w.Count }

// ------- Bicycle
type Bicycle struct {
    Name   string
    Wheels wheels // Use interface instead of concrete type
}

type BicycleWheels struct {
    Count int
}

func (b *Bicycle) GetName() string          { return b.Name }
func (b *Bicycle) GetWheels() wheels        { return b.Wheels }
func (w *BicycleWheels) GetCount() int      { return w.Count }

// ------- Common Interfaces -------
type wheels interface {
    GetCount() int
}

type vehicle interface {
    GetName() string
    GetWheels() wheels
}

// ------- Vehicle
type Vehicle struct {
    Name string
}

func Map2Vehicle(v vehicle) *Vehicle {
    return &Vehicle{
        Name: fmt.Sprintf("%s with %d wheels", v.GetName(), v.GetWheels().GetCount()),
    }
}

func main() {
    c := Car{Name: "MyCar", Wheels: &CarWheels{Count: 4}} // Pass a pointer to CarWheels
    b := Bicycle{Name: "MyBicycle", Wheels: &BicycleWheels{Count: 2}} // Pass a pointer to BicycleWheels
    
    v1 := Map2Vehicle(&c)
    v2 := Map2Vehicle(&b)
    fmt.Println(v1.Name, v2.Name)
}

字符串
现在,代码应该编译并生成您期望的输出:

MyCar with 4 wheels MyBicycle with 2 wheels

相关问题