结构成员的Golang func指针

Wil*_*ier 4 methods pointers go

给出以下类型:

type A struct {
    ...
}

func (a *A) Process() {
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

Process()方法不会修改实例A,我在方法接收器上使用指针,因为结构非常大.我的问题背后的想法是避免Process()在结构外声明函数并向其传递大量参数(而不是访问结构的成员).

edu*_*911 7

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

type Process func(a *A)
Run Code Online (Sandbox Code Playgroud)

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

func Test(p Process)
Run Code Online (Sandbox Code Playgroud)

要记住的一件事是,这些定义完全相同:

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

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

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

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

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

这允许通过分解我的结构(如您所说的“相当大”)来更轻松地进行单元测试,从而允许我仅模拟支持该方法所需的功能接口。

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

func(a *A)
Run Code Online (Sandbox Code Playgroud)

正如您所要求的,这可以用作另一种方法的输入,如下所示:

func AnotherFunc(fn func(a *A)) {
  a := &A{}
  fn(a)
}
Run Code Online (Sandbox Code Playgroud)

但对我来说,这让事情变得有点难以阅读,更不用说脆弱了——有人可以在那里更改 func 定义并在其他地方破坏其他东西。

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

type Process func(a *A)
Run Code Online (Sandbox Code Playgroud)

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

func AnotherFunc(p Process) {
  a := &A{}
  p(a)
}
Run Code Online (Sandbox Code Playgroud)

这允许您p作为指向 func 的指针访问,随意传递。(但请注意,您不必访问.IOW的实际指针p,不要这样做,&p因为func无论如何在 Golang 中类型都是通过引用传递的,就像slices和一样maps。)

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

工作示例

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

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)
}
Run Code Online (Sandbox Code Playgroud)

单元测试

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

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

var Process = func(a *A)
Run Code Online (Sandbox Code Playgroud)

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

 func Test(p Process)
Run Code Online (Sandbox Code Playgroud)

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

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 
}
Run Code Online (Sandbox Code Playgroud)

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


Joh*_*don 6

你甚至可以直接这样做,没有接口:

package main

import "fmt"

type A struct {
    Name string
}

func (a *A) Go() {
    fmt.Printf("GO: %v\n", 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)
}
Run Code Online (Sandbox Code Playgroud)

输出:

GO: &{FOO}
GO: <nil>
GO: &{}
Run Code Online (Sandbox Code Playgroud)

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