golang 挑战:编写函数walk(x interface{}, fn func(string))
,参数为结构体x
,并对x
中的所有字符串字段调用fn
函数。难度级别:递归。
计算中的反射提供了程序检查自身结构体的能力,特别是通过类型,这是元编程的一种形式。这也是造成困惑的一个重要原因。
interface
?string
,int
以及我们自己定义的类型,如 BankAccount
,我们享受到了 Go 为我们提供的类型安全。interface{}
来解决这个问题,你可以将其视为 任意 类型。walk(x interface{}, fn func(string))
的 x
参数可以接收任何的值。interface
类型来得到真正灵活的函数呢?interface
将失去对类型安全的检查。如果你想传入 string
类型的 Foo.bar
但是传入的是 int
类型的 Foo.baz
,编译器将无法通知你这个错误。你也搞不清楚函数允许传递什么类型的参数。知道一个函数接收什么类型,例如 UserService
,是非常有用的。interface
类型,这里容易让人困惑)设计它,以便用户可以用多种类型来调用你的函数,这些类型实现了函数工作所需要的任何方法。struct
来调用我们的函数,这个 struct
中有一个字符串字段(x
),然后我们可以监视传入的函数(fn
),看看它是否被调用。got
),字符串通过 walk
传递到 fn
。在前面的章节中,通常我们会专门为函数或方法调用指定类型,但在这种情况下,我们可以传递一个匿名函数给 fn
,它会隐藏 got
。string
类型的 Name
字段的匿名 struct
,以此得到最简单的实现路径。walk
并传入 x
参数,现在只检查 got
的长度,一旦有了基本的可以运行的程序,我们的断言就会更加具体。walk
函数fn
函数来使测试通过。fn
是如何被调用的做一个更具体的断言。fn
函数的字符串是否正确。x
并尝试查看它的属性。panic
String()
,它以字符串的形式返回底层值,但是我们知道,如果这个字段不是字符串,程序就会出错。fn
调用的字符串数组。value
有一个方法 NumField
,它返回值中的字段数。这让我们遍历字段并调用 fn
通过我们的测试。walk
的另一个缺点是它假设每个字段都是 string
。让我们为这个场景编写一个测试。string
。struct
怎么办?换句话说,如果我们有一个包含嵌套字段的 struct
会发生什么?struct
的结构。Kind
如果它碰巧是一个 struct
我们就在内部 struct
上再次调用 walk
。switch
会提高可读性,使代码更易于扩展。Value
不能使用 NumField
方法,在执行此方法前需要调用 Elem()
提取底层值。reflect.Value
的功能,将 interface{}
传入函数并返回这个值x
的 reflect.Value
,这样我就可以检查它,我不在乎怎么做。reflect.Value
中调用 NumField
。但它没有,因为它不是结构体。walk
return
来停止执行剩余的代码),如果不是,我们就假设它是 struct
。struct
或切片,我们会遍历它的值,并对每个值调用 walk
函数。如果是 reflect.String
,我们就调用 fn
。walk
的重复操作,但概念上它们是相同的。value
是一个 reflect.String
,我们就像平常一样调用 fn
。switch
将根据类型提取两个内容Value
(Field
或 Index
)numberOfValues
,使用 getField
函数的结果调用 walk
函数。map
。map
和 struct
很相似,只是编译时的键是未知的。map
中获取值。它只能通过 键 来完成,这样就打破了我们的抽象,该死。walkValue
,它依照「Don't repeat yourself」的原则在 switch
中调用 walk
函数,这样它们就只需要从 val
中提取 reflect.Value
即可。map
不能保证顺序一致。因此,你的测试有时会失败,因为我们断言对 fn
的调用是以特定的顺序完成的。map
的断言移动到一个新的测试中,在这个测试中我们不关心顺序。assertContains
是如何定义的reflect
包中的一些概念。