Golang中如何强制类型检查并判断对象是否实现了接口?

Golang中如何强制类型检查并判断对象是否实现了接口? 以下代码片段令人困惑。问题是如何确保

package main

import (
	"fmt"
)

type PersonAttributes struct {
	firstName string
}

type Behaviour interface {
	Run()
	Walk()
}

type Manager struct {
	mapper map[string]interface{}
}

func NewManager() Manager {
	return Manager{
		mapper: make(map[string]interface{}),
	}
}

func (m *Manager) AddToManager(d interface{}) {
	m.mapper["a"] = d

}

func NewPerson() PersonAttributes {

	return PersonAttributes{
		firstName: "Atul",
	}
}

func (s *PersonAttributes) Run() {
	fmt.Println("Running")

}

func (s *PersonAttributes) Walk() {
	fmt.Println("Walking")

}

func main() {

	obj := NewPerson()
	manager := NewManager()
	manager.AddToManager(obj)

	//这段代码正在运行且预期会运行
	//obj.Run()

	//尽管Person定义了行为,但这仍然失败
	if rt, okk := manager.mapper["a"].(Behaviour); okk {
		//问题1:为什么这个代码块没有运行?
		//问题2:如何让这个代码块运行?

		rt.Run()
	}
}

https://play.golang.org/p/FOuIft2YQDk


更多关于Golang中如何强制类型检查并判断对象是否实现了接口?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

非常感谢 @acim。现在非常清楚了。

更多关于Golang中如何强制类型检查并判断对象是否实现了接口?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


非常感谢。我正在尝试深入理解这个问题。

为什么这里的返回类型作为指针很重要? 传递的是"哪个"对象为什么会产生影响? 为什么不是任何实现了这些函数的对象都能通过类型检查?

这是因为 NewPerson() 返回的是 PersonAttributes 而不是 *PersonAttributes

如果将 NewPerson() 修改为以下形式,就能正常运行:

func NewPerson() *PersonAttributes {

	return &PersonAttributes{
		firstName: "Atul",
	}
}

您将值类型 PersonAttributes 添加到 Manager 中,而您的方法定义在指针接收器上,这意味着值接收器上没有行为方法。您需要在 PersonAttributes(而不是 *PersonAttributes)上定义 Run 和 Walk 方法,或者使用指针:

func NewPerson() *PersonAttributes {

	return &PersonAttributes{
		firstName: "Atul",
	}
}

顺便提一下,在 Go 语言中,名为 NewSomething 的函数通常返回指针。

我在上面的帖子中加了更多注释,但可能还是不够清楚。在Go语言中,你可以为值类型或指针类型定义方法,这两者并不相同。如果你像现在这样给指针接收器添加方法,那么你需要用指针而不是值来检查接口实现。反之,如果你为值接收器定义方法,则应该用值来检查接口。

不要混用方法类型,如果你有指针接收器的方法,就让该对象的所有方法都基于指针定义,反之亦然。

最后,Go语言中的NewSomething函数应该返回指针而不是值,这才是预期的行为。

func main() {
    fmt.Println("hello world")
}

在Go语言中,类型断言失败是因为PersonAttributes的方法接收器是指针类型,而存储在manager.mapper["a"]中的是值类型

问题分析

当调用NewPerson()时返回的是PersonAttributes值类型,而Run()Walk()方法的接收器是*PersonAttributes(指针类型)。这意味着:

  • PersonAttributes值类型不实现Behaviour接口
  • *PersonAttributes指针类型实现Behaviour接口

解决方案

方案1:存储指针到Manager中

func main() {
    obj := NewPerson()
    manager := NewManager()
    // 存储指针而不是值
    manager.AddToManager(&obj)
    
    if rt, okk := manager.mapper["a"].(Behaviour); okk {
        rt.Run() // 现在这会正常运行
    }
}

方案2:修改NewPerson返回指针

func NewPerson() *PersonAttributes {
    return &PersonAttributes{
        firstName: "Atul",
    }
}

func main() {
    obj := NewPerson() // obj现在是指针类型
    manager := NewManager()
    manager.AddToManager(obj)
    
    if rt, okk := manager.mapper["a"].(Behaviour); okk {
        rt.Run() // 这会正常运行
    }
}

方案3:使用值接收器定义方法

// 将方法接收器改为值类型
func (s PersonAttributes) Run() {
    fmt.Println("Running")
}

func (s PersonAttributes) Walk() {
    fmt.Println("Walking")
}

func main() {
    obj := NewPerson() // 值类型
    manager := NewManager()
    manager.AddToManager(obj)
    
    if rt, okk := manager.mapper["a"].(Behaviour); okk {
        rt.Run() // 现在这会正常运行
    }
}

类型检查的最佳实践

// 明确的类型检查函数
func CheckBehaviour(obj interface{}) (Behaviour, bool) {
    if behaviour, ok := obj.(Behaviour); ok {
        return behaviour, true
    }
    return nil, false
}

// 或者在Manager中添加方法
func (m *Manager) GetBehaviour(key string) (Behaviour, bool) {
    if obj, exists := m.mapper[key]; exists {
        if behaviour, ok := obj.(Behaviour); ok {
            return behaviour, true
        }
    }
    return nil, false
}

// 使用示例
func main() {
    obj := NewPerson()
    manager := NewManager()
    manager.AddToManager(&obj) // 存储指针
    
    if behaviour, ok := manager.GetBehaviour("a"); ok {
        behaviour.Run()
        behaviour.Walk()
    }
}

关键点:在Go中,值类型和指针类型在实现接口时被认为是不同的类型。如果方法使用指针接收器定义,只有该类型的指针才能满足接口要求。

回到顶部