【问题标题】:Is there a way to use reflect to discern type-relationships in Go?有没有办法使用反射来识别 Go 中的类型关系?
【发布时间】:2021-04-27 17:41:50
【问题描述】:

上下文

为 Go 语言库编写 URL 查询参数解析器

问题

据我所知,Go 中只有结构具有继承形式。 可以使用反射包来识别实体的种类,即它是类型系统中的基本存储类,并且可以探测所述元素的类型。所以我可以辨别实体是 Kind 字符串,并且 Type Title,作为一个任意的例子,假设存在这样的东西:

type Title string

更好的是,对于结构,我可以使用匿名成员来获得有限类型的继承:

type Foo struct { name string }
func (f Foo) Hello() string { return f.name; }

type Bar struct { Foo }

func main() {
  b := Bar{ Foo{"Simon"} }
  fmt.Println(b.Hello())
}

Live example

重点是,Go 允许我将 Foo 扩展为 Bar,但至少对于 Foo 的部分继承/重用 Foo 函数。

但是,对于非结构类型 - 我不知道如何处理类似于 json 或 xml 的编码/解码库的这个问题 - 我希望能够将查询参数解码为结构的成员,并且至关重要的是,能够支持用户定义的类型,而不需要每个人都为我的目的定义一个专门的解码器或编码器,只要它们是支持我可以使用的接口的类型的派生。

具体来说,我希望支持存储为 Google uuid.UUID 或 time.Time(或 time.Duration 也很有用)的用户定义类型。

这是一个简单的例子:

type TransactionID uuid.UUID

正如声明的那样,这继承了 uuid.UUID 的零行为,我不确定是否或如何使用反射来故意在我的编码器/解码器库中实现这一点?

Go 本身不会提供它们之间的任何继承或关系。我的 TransactionID 不是 uuid.UUID,但它们共享同一种类型 - 一个 [16] 字节数组,据我目前所知,这就是它们共享的全部内容。

这是我的难题

如何允许我希望支持的用户定义类型不要求我的用户现在为他们的类型定义我已经为“基本”类型定义的相同编码器或解码器函数?

更多上下文

我的解码器库使这个接口可用:

// Parser is an interface for types that RequestParser can parse from a URL parameter
type Parser interface {
    ParseParameter(value string) error
}

我已经为 uuid.UUID 和 time.Time 以及其他一些有用的非结构类型定义了一个专门的扩展,以便从查询参数字符串中解码它们。如果我要解码的实体类型实际上是 uuid.UUID 或 time.Time 而不是基于这些的用户定义类型,那么一切正常。同样,用户别名之所以有效,是因为它们不是真正的新类型。

别名对于我的目的来说太有限了——它们与别名类型没有任何有意义的区别,这严重限制了它们的实际效用。我不会回应需要使用它们的建议。谢谢。

更新

理想情况下,用户应该能够为他们的目的定义 TransactionID,我想知道它是“一种”UUID,因此 - 默认情况下 - 使用 UUID 解析器。

用户已经可以轻松定义:

func (id *TransactionID) ParseParameter(value string) (err error) {
   id_, err := uuid.Parse(value)
   if err != nil {
      return
   }
   *id = TransactionID(id_)
   return
}

这将迫使我的解码器选择他们定义明确的接口 - 并完全按照他们希望的类型对输入进行解码。

我希望 - 有一种方法知道他们的类型是____什么的派生类型?,如果我有一个____什么的解析器 - 那么 - 在没有用户定义的 ParseParameter 的情况下 - - 使用我默认提供的智能设备(它知道 [16] 字节与 UUID 不同,并且了解更复杂的解码 UUID 的方法)。

附录

当我得到深思熟虑和有用的答复时,我将扩展此问题并提供更多详细信息。我现在不确定我还能提供什么?

感谢您抽出宝贵时间认真回复,如果您选择这样做的话。

【问题讨论】:

  • 当您将一种类型嵌入到另一种类型中时,封闭类型将获得与嵌入类型相同的方法。当您基于另一个命名类型定义新的命名类型时,新类型不会获得基类型的方法。第二个不是类型别名。你的问题不清楚。 “关系”是什么意思?
  • “我知道只有结构在 Go 中具有继承形式。” ——嗯,不是真的。 Go 中根本没有继承性。但是有嵌入,也许这就是你所说的“一种形式”。但是您可以嵌入 any 类型,而不仅仅是结构,但只能嵌入 结构中。
  • 如果你想支持更多类型,然后在接口中封装一个用于转换和编码的逻辑,并将处理程序存储在解析器中,然后循环遍历它们,或者只存储一个并让用户决定他们想要的后端使用。
  • 真正问题的答案:反射 API 无法告诉您 A 被定义为类型 B,因为除了共享的底层类型之外,类型 A 和 B 之间没有任何关系。使用结构嵌入来声明一个与其他类型共享方法的新类型。
  • @Mordachai 允许转换,因为基础类型相同。 TransactionID 和 uuid UUID 之间没有其他关系。

标签: go decoding reflect


【解决方案1】:

无法使用反射 API 来辨别一种类型是从另一种类型创建的。

specification says:

类型定义创建一个新的、不同的类型,它具有与给定类型相同的underlying type 和操作,并将标识符绑定到它。

新类型称为已定义类型。它是来自任何其他类型的different,包括创建它的类型。

规范没有定义定义类型和创建它的类型之间的任何其他关系。反射 API 没有要公开的“创建自”关系。

在示例中

type A B

反射 API 无法告诉您类型 A 是从类型 B 创建的,因为除了共享的底层类型之外,类型 A 和类型 B 之间没有任何关系。

Type conversions 从 A 到 B 和 B 到 A 是允许的,因为 A 和 B 具有相同的底层类型。

您可以使用反射 API 来确定两种类型是否具有相同的底层类型,但这无助于解决问题。可能 A 是从 B 创建的,B 是从 A 创建的,或者两者都不是从另一个创建的。在此示例中,类型 A 和 B 具有相同的基础类型:

 type A int
 type B int

问题表明解析器有一个用于uuid.UID 的内置解码器。目标是将该解析器用于type TransactionID uuid.UUID。以下是解决该问题的一些方法:

嵌入将类型声明为type TransactionID struct { uuid.UID }。检查具有单个已知嵌入字段的结构并进行相应处理。

Registry添加函数函数来注册可转换类型之间的映射。

 var decodeAs  = map[reflect.Type]reflect.Type{}
 
 func RegisterDecodeAs(target, decode reflect.Type) {
     if !decode.ConvertibleTo(target) {
        panic("type must be convertible")
     }
     decodeAs[target] = decode
 }

解码时,检查decodeAs中的解码类型。如果存在,则解码为解码类型,转换为目标类型并赋值。

在程序启动时调用注册函数:

 func init() {
     RegisterDecodeAs(reflect.TypeOf(TransactionID(nil)), reflect.TypeOf(uuid.UUID(nil))
 }

【讨论】:

  • 是的,这也是我的想法(这两个想法 - 使用注册表,或嵌入 UUID,而不是从中创建新类型)。我将为用户提供一个 UUID,它是一个带有嵌入式 uuid.UUID 和默认解析器的结构。这样就可以轻松创建新的用户类型,同时保留 UUID 共有的默认行为(或者它们可以定义自己的覆盖,很简单)。感谢您的回复 - 我认为它写得很好,可能会帮助其他人继续前进。干杯。
  • 我实际上可能两者都做。它将为最终用户提供更多的弹性/选择来支持任何一个,并且可能有我没有立即预见到的用途,但仍然很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-18
相关资源
最近更新 更多