Golang 挑战:编写函数 walk(x interface{}, fn func(string)),参数为结构体 x,并对 x 中的所有字符串字段调用 fn 函数。难度级别:递归。...

经过一段时间的学习与实践,针对 Golang 语言基础的 TDD 驱动开发测试训练已经告一段落,接下来会在此基础上继续使用 TDD 来构建应用程序 。

博主前一部分的代码Github先贴下面👇以供参考

https://github.com/slowlydance2me/My_Golang_Study.git

2023-03-29 今日更新

golang 挑战:编写函数 walk(x interface{}, fn func(string)),参数为结构体 x,并对 x 中的所有字符串字段调用 fn 函数。难度级别:递归。

为此,我们需要使用 反射

计算中的反射提供了程序检查自身结构体的能力,特别是通过类型,这是元编程的一种形式。这也是造成困惑的一个重要原因。

什么是 interface

由于函数使用已知的类型,例如 stringint 以及我们自己定义的类型,如 BankAccount,我们享受到了 Go 为我们提供的类型安全。
这意味着我们可以免费获得一些文档,如果你试图向函数传递错误的类型,编译器就会报错。
但是,你可能会遇到这样的情况,即你不知道要编写的函数参数在编译时是什么类型的。
Go 允许我们使用类型 interface{} 来解决这个问题,你可以将其视为 任意 类型。
所以 walk(x interface{}, fn func(string))x 参数可以接收任何的值。

那么为什么不通过将所有参数都定义为 interface 类型来得到真正灵活的函数呢?

  • 作为函数的使用者,使用 interface 将失去对类型安全的检查。如果你想传入 string 类型的 Foo.bar 但是传入的是 int 类型的 Foo.baz,编译器将无法通知你这个错误。你也搞不清楚函数允许传递什么类型的参数。知道一个函数接收什么类型,例如 UserService,是非常有用的。
  •  作为这样一个函数的作者,你必须检查传入的 所有 参数,并尝试断定参数类型以及如何处理它们。这是通过 反射 实现的。这种方式可能相当笨拙且难以阅读,而且一般性能比较差(因为程序必须在运行时执行检查)。
简而言之,除非真的需要否则不要使用反射。
如果你想实现函数的多态性,请考虑是否可以围绕接口(不是 interface 类型,这里容易让人困惑)设计它,以便用户可以用多种类型来调用你的函数,这些类型实现了函数工作所需要的任何方法。
我们的函数需要能够处理很多不同的东西。和往常一样,我们将采用迭代的方法,为我们想要支持的每一件新事物编写测试,并一路进行重构,直到完成。

首先编写测试

我们想用一个 struct 来调用我们的函数,这个 struct 中有一个字符串字段(x),然后我们可以监视传入的函数(fn),看看它是否被调用。

func TestWalk(t *testing.T) {

    expected := "Chris"
    var got []string

    x := struct {
        Name string
    }{expected}

    walk(x, func(input string) {
        got = append(got, input)
    })

    if len(got) != 1 {
        t.Errorf("wrong number of function calls, got %d want %d", len(got), 1)
    }
}
  • 我们想存储一个字符串切片( got),字符串通过 walk 传递到 fn。在前面的章节中,通常我们会专门为函数或方法调用指定类型,但在这种情况下,我们可以传递一个匿名函数给 fn,它会隐藏 got
  •   我们使用带有 string 类型的 Name 字段的匿名 struct,以此得到最简单的实现路径。
  •  最后调用 walk 并传入 x 参数,现在只检查 got 的长度,一旦有了基本的可以运行的程序,我们的断言就会更加具体。

尝试运行测试

./reflection_test.go:21:2: undefined: walk

为测试的运行编写最小量的代码,并检查测试的失败输出

我们需要定义 walk 函数

func walk(x interface{}, fn func(input string)) {

}

再次尝试运行测试

=== RUN TestWalk
--- FAIL: TestWalk (0.00s)
reflection_test.go:19: wrong number of function calls, got 0 want 1
FAIL

编写足够的代码使测试通过

我们可以使用任意的字符串调用 fn 函数来使测试通过。

func walk(x interface{}, fn func(input string)) {
    fn("I still can't believe South Korea beat Germany 2-0 to put them last in their group")
}

现在测试应该通过了。接下来我们需要做的是对我们的 fn 是如何被调用的做一个更具体的断言。

首先编写测试

在之前的测试中添加以下代码,检查传入 fn 函数的字符串是否正确。

if got[0] != expected {
    t.Errorf("got '%s', want '%s'", got[0], expected)
}

尝试运行测试

=== RUN TestWalk
--- FAIL: TestWalk (0.00s)
reflection_test.go:23: got 'I still can't believe South Korea beat Germany 2-0 to put them last in their group', want 'Chris'
FAIL

编写足够的代码使测试通过

func walk(x interface{}, fn func(input string)) {
    val := reflect.ValueOf(x)
    field := val.Field(0)
    fn(field.String())
}

这段代码 非常不安全,也非常幼稚,但请记住,当我们处于「红色」状态(测试失败)时,我们的目标是编写尽可能少的代码。然后我们编写更多的测试来解决我们的问题。

我们需要使用反射来查看 x 并尝试查看它的属性。
反射包有一个函数 ValueOf,该函数值返回一个给定变量的 Value。这为我们提供了检查值的方法,包括我们在下一行中使用的字段。
然后我们对传入的值做了一些非常乐观的假设:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值