结构成员的Golang函数指针

toiithl6  于 2022-12-07  发布在  Go
关注(0)|答案(3)|浏览(148)

给定以下类型:

type A struct {
    ...
}

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

我想将A类型的方法Process传递给另一个函数,并能够访问A的底层示例的内容。
我应该如何将这个方法传递给另一个函数?通过指针?以及它应该如何被调用?
Process()方法不会修改A的示例,我在方法接收器上使用了一个指针,因为结构体相当大。我的问题背后的想法是避免在结构体外部声明函数Process(),并向其传递大量参数(而是访问结构体的成员)。

zynd9foi

zynd9foi1#

您甚至可以直接执行此操作,而无需界面:

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

输出量:

GO: &{FOO}
GO: <nil>
GO: &{}

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

rsaldnfx

rsaldnfx2#

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

type Process func(a *A)

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

func Test(p Process)

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

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

指针接收器就是一个函数,它的局部变量是指向结构体的指针,而值接收器就是一个函数,它的局部变量是指向结构体的值副本。
为什么不使用结构体上的方法,比如选项1呢?原因有很多。将相关方法组合到结构体本身上,比如选项1,看起来确实很直观(特别是来自其他OOP语言,如Java或.NET,在这些语言中,您通常会在单个结构体上添加1000个方法)。但是,由于您指出struct本身非常大,这闻起来有SoC的味道(它太大了),可能需要分解。
就我个人而言,我在使用上述选项2时遵循的规则是:

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

这使得单元测试变得更加容易,因为它分解了我的结构,正如你所说的那样,它“相当大”,允许我只模拟支持该方法所需的功能的接口。
现在,函数定义本身就是类型。到目前为止,我们有这样的类型:

func(a *A)

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

func AnotherFunc(fn func(a *A)) {
  a := &A{}
  fn(a)
}

但对我来说,这让事情有点难以阅读更不用说脆性-有人可以改变那里的函数定义,并打破其他地方的其他东西。
这是我喜欢定义类型的地方:

type Process func(a *A)

这样我就可以像这样吃了

func AnotherFunc(p Process) {
  a := &A{}
  p(a)
}

这允许你访问p作为你指向函数的指针,你可以随意传递。(注意,你不必访问p的实际指针。IOW,不要这样做&p,因为func类型在Golang中是通过引用传递的,就像slicesmaps一样。)
总的来说,当您希望将逻辑分解为更小的可管理(和更易测试)部分时,通常会遵循这种模式--通过使用更小的、更易管理的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)
}

单元测试

将函数类型的概念提升到另一个层次,可以简化单元测试。
可以为Process()函数定义全局变量:

var Process = func(a *A)

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

func Test(p Process)

现在的区别是在单元测试期间,您可以覆盖函数:

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 
}

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

oyxsuwqo

oyxsuwqo3#

您可以使用以下界面实现此目的:

type Processor interface {
    Process()
}

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

...
var a A
anotherFunction(a)

相关问题