给定以下类型:

1
2
3
4
5
6
7
type A struct {
    ...
}

func (a *A) Process() {
    ...
}

我想将类型A的方法process传递给另一个函数,并能够访问A的基础实例的内容。

我应该如何将方法传递给另一个函数? 通过指针? 怎么称呼它呢?

Process()方法不会修改A的实例,因为结构很大,所以我在方法接收器上使用了指针。 我的问题背后的想法是避免在结构外部声明函数Process(),并向其传递大量参数(相反,它可以访问结构的成员)。


您甚至可以直接进行操作,而无需使用界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import"fmt"

type A struct {
    Name string
}

func (a *A) Go() {
    fmt.Printf("GO: %v\
", a)
}

func Test(fn func()) {
    fn()
}

func main() {
    aa := &A{Name:"FOO"}
    bb := (*A)(nil)
    cc := &A{}
    Test(aa.Go)
    Test(bb.Go)
    Test(cc.Go)
}

输出:

1
2
3
GO: &{FOO}
GO: <nil>
GO: &{}

在操场上:https://play.golang.org/p/V-q2_zwX8h


另一个选择是将func定义为类型:

1
type Process func(a *A)

然后在调用其他函数时将其用作参数:

1
func Test(p Process)

要记住的一件事是这些定义是完全一样的:

  • func (a *A) Process() { a.MyVar }
  • func Process(a *A) { a.MyVar }

指针接收器只是一个带有指向该结构的指针的局部变量的函数。相反,值接收器是一个带有局部变量的函子,该局部变量对应于该结构的值副本。

为什么不使用结构上的方法,如选项1?原因有很多。像选项1一样,将相关方法分组到结构本身上似乎很直观(特别是如果来自其他OOP语言,如Java或.NET,通常在单个结构上粘贴上万个方法)。但是,由于您说struct本身很大,所以它闻到SoC的味道(太大),可能需要分解。

就个人而言,使用上述选项2时遵循的规则是:

  • 如果func没有使用整个结构的属性(例如,它仅对数据的子集进行操作,甚至根本不操作),则我将选项2与指针一起使用。 (或者,将接口本身使用零字节结构)

通过分解您所说的"很大"的结构,这使单元测试更加容易,使我可以仅模拟支持该方法所需的功能接口。

现在,根据定义,func定义本身就是类型。到目前为止,我们有这种类型:

1
func(a *A)

可以将其用作您要求的另一种方法的输入,例如:

1
2
3
4
func AnotherFunc(fn func(a *A)) {
  a := &A{}
  fn(a)
}

但是对我来说,这使事情很难阅读,更不用说它很脆了–有人可以在那里更改func定义,并破坏其他地方的东西。

这是我更喜欢定义类型的地方:

1
type Process func(a *A)

这样,我可以像这样消费它:

1
2
3
4
func AnotherFunc(p Process) {
  a := &A{}
  p(a)
}

这样,您就可以访问p作为指向函数的指针,并根据需要进行传递。 (不过请注意,您不必访问p的实际指针。IOW,请不要执行此&p,因为func类型无论如何都是通过引用在Golang中传递的,就像slices。)

总体而言,当您想将逻辑分解成较小的可管理(和可测试性更高)的部分时,通常会遵循这种模式-通过使用较小,更易于管理的AnotherFunc()方法来导出和进行API合同的单元测试,同时隐藏内部。

工作实例

http://play.golang.org/p/RAJ2t0nWEc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import"fmt"

type Process func(a *A)

type A struct {
  MyVar string
}

func processA(a *A) {
  fmt.Println(a.MyVar)
}

func AnotherFunc(a *A, p Process) {
  p(a)
}

func main() {

    a := &A{
        MyVar:"I'm here!",
    }

    AnotherFunc(a, processA)
}

单元测试

将func类型的概念提升到另一个层次,将是简化单元测试。

您可以为Process()函数定义全局变量:

1
var Process = func(a *A)

它将继续以完全相同的方式使用:

1
 func Test(p Process)

现在的区别是在单元测试期间,您可以覆盖该功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package mypackage_test

import"testing"

func TestProcessHasError(t *testing.T) {
    // keep a backup copy of original definition
    originalFunctionality := Process

    // now, override it
    Process = func(a *A) error {
        // do something different here, like return error
        return errors.New("force it to error")
    }

    // call your Test func normally, using it
    err := Test(Process)

    // check for error
    assert.Error(err)

    // be a good tester and restore Process back to normal
    Process = originalFunctionality
}

当我接触到现有的代码库时,这些就是我开始实现的一些技巧,以帮助使应用程序与自身脱钩-并允许进行更多测试。

  • 尽管它不能直接回答问题,但我感谢您付出的努力,也谢谢您,因为它非常有用且很有帮助。

您可以通过以下接口实现此目的:

1
2
3
4
5
6
7
8
9
10
11
type Processor interface {
    Process()
}

func anotherFunction(p Processor) {
    p.Process()
}

...
var a A
anotherFunction(a)