在Go中,一种类型被强制转换为另一种类型,可以用一种方法来确定接收器的类型吗?

时间:2011-06-26 14:00:59

标签: types go

如果类型T1T2基于类型T,则类型T仅来自NewT1()NewT2() ,函数func (*T) WhoAmI()是否可以知道“真的”是T1还是T2

package main
import "fmt"
import "reflect"

type T struct { s string }
func (v *T) WhoAmI() string {

        // pull type name with reflect  
        fmt.Println( reflect.TypeOf(v).Elem().Name() )  // always prints "T"!

        // todo: if I am actually T1
        return "T1"
        // todo: else if I am actually T2
        return "T2"
}

type T1 T
func NewT1( s string ) T1 { return T1{ s } }

type T2 T
func NewT2( s string ) T2 { return T2{ s } }

func main() {
        var t1 = T1{ "xyz" }
        var t2 = T2{ "pdq" }
        s1 := ((*T)(&t1)).WhoAmI()      // would like to return "T1"
        s2 := ((*T)(&t2)).WhoAmI()      // would like to return "T2"
        fmt.Println( s1, s2 )
}

从技术上讲:

一旦t1类型T1被强制转换为T类型,因此可以调用func (*T) WhoAmI()t1是否完全失去了其类型真正T1的事实{1}}?如果没有,我们如何从接收类型T的方法的角度回收知识?

一般来说:

换句话说,如果一个类型基于另一个类型,如果派生类型的变量被强制转换为基类型以运行方法,那么该方法是否可以学习调用它的接收者的真实类型?

2 个答案:

答案 0 :(得分:8)

不,这是不可能的。从旧类创建新类型与创建从基于类的语言中的父类继承的新类不同。在你的情况下,T对T1或T2一无所知,如果你正在调用WhoAmI方法,你可以根据定义使用类型为T的接收器。

您的设计可能会更好地使用界面。尝试更像这样的东西:

type T interface {
    WhoAmI() string
}

type T1 struct {
    s string
}

func (t *T1) WhoAmI() string { return "T1" }

type T2 struct {
    s string
}

func (t *T2) WhoAmI() string { return "T2" }

Go playground

上试试

T1和T2都实现了接口T,因此它们可以用作类型T.

答案 1 :(得分:4)

Evan的回答很好。但是,有多种方法可以解决这个问题,更接近您的目标。

当你转换时,你实际上改变了类型,没有任何残余。 Go只关心当前类型是什么。

解决这个问题的一种方法就是编写一个函数。函数对于共享实现非常有用。一些面向对象的语言将它们抛弃为不纯净,但是它们不知道它们缺少什么(我正在看着你的公共静态虚空!)。

func WhoAmI(v interface{}) string {
    switch v.(type) {
    case *T: return "*T"
    case *T1: return "*T1"
    case *T2: return "*T2"
    }

    return "unknown"
}

现在您不必转换值以调用方法/函数。当然,如果您要进行类型切换并为每种类型执行不同的操作,您也可以为每种类型编写不同的方法。

要使其成为一种方法,您可以这样做:

type T struct { s string }
func (t *T) WhoAmI() string { return WhoAmI(t) }

type T1 T
func (t1 *T1) WhoAmI() string { return WhoAmI(t1) }

这样,您无需重新实现该方法。

如果你真的希望T知道自己,那就自己动手吧!有两种方法可以做到这一点。一个是作为参数:

func (t *T) WhoAmI(self interface{}) string { ... }
...
fmt.Println(t.WhoAmI(t))
fmt.Println(((*T)(t1)).WhoAmI(t1))

这样做的好处是你不需要做任何额外的工作。该方法可以同时访问t和self,因此它具有两全其美的优点。但是,这会成为界面的一部分,这有点尴尬。

你也可以把它变成一个字段:

type T struct { self interface{} }
func NewT() *T {
    t := new(T)
    t.self = t
    return t
}

type T1 T
func NewT1() *T1 {
    t1 := new(T1)
    t1.self = t1
    return t1
}

现在,TT1上的所有方法都可以通过检查self来判断最初创建对象的内容。

可以来回转换来获取方法,或者你可以使用一个名为embedding的功能:

type T struct{}
func (t *T) M() {}

type T1 struct { T }
...
var t T
var t1 T1
t.M()
t1.M()

如您所见,您可以通过T.Mt致电t1。不过请注意,T.M始终只会看到T,无论您将其称为t还是t1。您必须使用上述策略之一才能让T.M能够看到T1