One type is coerced into another, can a method to determine the type of the receiver?

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

One type is coerced into another, can a method to determine the type of the receiver?

问题

如果类型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 {

	// 使用反射获取类型名称
	fmt.Println(reflect.TypeOf(v).Elem().Name()) // 总是打印"T"!

	// 待办事项:如果我实际上是T1
	return "T1"
	// 待办事项:否则如果我实际上是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() // 希望返回"T1"
	s2 := ((*T)(&t2)).WhoAmI() // 希望返回"T2"
	fmt.Println(s1, s2)
}

从技术上讲:

一旦t1类型T1被强制转换为类型T,以便调用func (*T) WhoAmI()t1是否完全失去了其实际类型为T1的事实?如果没有,从接收类型为T的方法的角度来看,我们如何恢复这个知识?

一般来说:

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

英文:

If types T1 and T2 are based on type T, and type T only comes into existence from a NewT1() or NewT2(), is there any way a function func (*T) WhoAmI() can know whether it "really" is a T1 or 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)
}

to speak technically:

once t1 type T1 is coerced into type T so func (*T) WhoAmI() can be called, does t1 completely lose the fact that its type is really T1? if not, how do we reclaim the knowledge from the perspective of a method receiving type T?

to speak generally:

in other words, if one type is based on another, if a variable of the derived type is coerced into the base type to run a method, can that method learn the real type of the receiver who called it?

答案1

得分: 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" }

在<kbd>Go playground</kbd>上尝试一下。

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

英文:

No, it's not possible. Creating a new type from an old one is not like creating a new class that inherits from a parent class in an class-based language. In your case T knows nothing about either T1 or T2 and if you're calling the WhoAmI method you have a receiver of type T by definition.

Your design might work better with an interface. Try something more like this:

type T interface {
    WhoAmI() string
}

type T1 struct {
    s string
}

func (t *T1) WhoAmI() string { return &quot;T1&quot; }

type T2 struct {
    s string
}

func (t *T2) WhoAmI() string { return &quot;T2&quot; }

Try it on the <kbd>Go playground</kbd>

T1 and T2 both implement the interface T, so they can be used as type T.

答案2

得分: 4

Evan的答案很好。然而,有多种解决这个问题的方法更接近你所期望的。

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

解决这个问题的一种方法是编写一个函数。函数非常有用,可以共享实现。一些面向对象的语言将它们视为不纯净的,但它们不知道它们错过了什么(我在看你,public static void!)。

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知道自己,给它一个self!有两种方法可以做到这一点。一种是作为参数:

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来确定对象最初是如何创建的。

可以不断地进行转换以获取方法,或者你可以使用一种称为嵌入的特性:

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

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

如你所见,你可以通过tt1调用T.M。然而,请记住,T.M始终只能看到一个T,无论你在哪个上调用它(tt1)。为了让T.M能够看到一个T1,你将不得不使用上述策略之一。

英文:

Evan's answer is good. However, there are multiple ways to solve this problem that are closer to what you were looking for.

When you convert, you actually change types, there's nothing residual. Go only cares what the current type is.

One approach to get around this is just to write a function. Functions are very useful for having shared implementation. Some object oriented languages threw them out as being impure, but they don't know what they're missing (I'm looking at you public static void!).

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

    return &quot;unknown&quot;
}

Now you don't have to convert the value in order to call the method/function. Of course, if you're going to do a type switch and do something different for each type, you might as well just write a different method for each type.

To make it a method, you could do:

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

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

This way, you don't need to reimplement the method.

If you really want T to know itself, give it a self! There are two ways of doing this. One is as a parameter:

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

The upside of this is that you don't need to do any extra work. The method has access to both t and self, so it has the best of both worlds. However, this becomes part of your interface, which is a little awkward.

You could also make it a field:

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
}

Now, any methods on a T or T1 can tell what the object was originally created as by checking self.

You can keep converting back and forth to get methods, or you can use a feature called embedding:

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

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

As you can see, you can call T.M via t or t1. However, keep in mind that T.M will always only see a T, regardless of what you call it on (t or t1). You'll have to use one of the above strategies in order for T.M to be able to see a T1.

huangapple
  • 本文由 发表于 2011年6月26日 22:00:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/6484420.html
匿名

发表评论

匿名网友

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

确定