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()在结构外声明函数并向其传递大量参数(而不是访问结构的成员).
另一种选择是将 定义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)
当我接触到现有的代码库时,这些是我开始实施的一些技巧,以帮助将应用程序与其自身解耦 - 并允许进行更多测试。
你甚至可以直接这样做,没有接口:
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