0

语境

为 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())
}

活生生的例子

关键是,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 的情况下 - 使用我的默认提供的 smart one(它知道 [16] 字节与 UUID 不同,并且理解解码 UUID 的更复杂的方法)。

附录

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

如果您选择这样做,感谢您花时间深思熟虑地做出回应。

4

1 回答 1

2

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

规范说:

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

新类型称为已定义类型。它不同于任何其他类型,包括创建它的类型。

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

在示例中

type A B

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

允许从 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. 以下是解决该问题的一些方法:

Embedding 将类型声明为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))
 }
于 2021-04-27T20:02:52.900 回答