为什么包通过通过一个导出的函数返回非导出的函数来导出非导出的函数?

huangapple go评论88阅读模式
英文:

Why do packages export a non-exported function by returning the non-exported function through an exported function

问题

为什么有些包会声明两个相同的函数,唯一的区别是一个被导出,另一个没有,但被导出的函数只是返回非导出函数的结果,像这样:

func Foo() {
    return foo()
}

func foo() {
   log.Println("Hello")
}

为什么不直接将日志记录放在被导出的函数中,然后删除多余的一行呢?显然有一个原因,但我并没有看到如果你可以在任何地方都使用被导出的函数,为什么还要这样做。谢谢!

这里是一个在生产环境中使用的示例:链接

英文:

Why do some packages declare two equal functions the only difference is one is exported and the other is not but the one that is exported just returns the non-exported function like this:

func Foo() {
    return foo()
}

func foo() {
   log.Println("Hello")
}

Why not just move the log into the exported function and get rid of the extra line? Obviously there is a reason but I don't really see one if you can just use the exported one everywhere. Thanks!

Example here of it being used in Production

答案1

得分: 5

你提到了两个例子。第一个例子(https://github.com/yohcop/openid-go/blob/master/verify.go#L11-L13):

func Verify(uri string, cache DiscoveryCache, nonceStore NonceStore) (id string, err error) {
    return verify(uri, cache, urlGetter, nonceStore)
}

你可以看到,未导出的verify函数接受了一个额外的urlGetter参数。这可能是该包的客户端不能或不应该提供的内容。导出的函数确定了包的客户端如何使用它;未导出函数的签名反映了verify函数执行所需的依赖关系。

第二个例子(https://github.com/golang/oauth2/blob/master/oauth2.go#L259-L266):

func StaticTokenSource(t *Token) TokenSource {
    return staticTokenSource{t}
}

// staticTokenSource is a TokenSource that always returns the same Token.
type staticTokenSource struct {
    t *Token
}

这限制了客户端如何构造staticTokenSource:只有一种方法可以通过StaticTokenSource构造函数来完成,不能直接通过结构体字面量来完成。这在许多情况下都很有用,例如输入验证。在这种情况下,你希望确保客户端无法更改对象上的t字段,为此,你将t字段设为未导出。但是当它未导出时,客户端将无法直接构造结构体字面量,因此你必须提供一个构造函数。

总的来说,当你可以限制访问、构造或修改方式时,代码会更容易理解。Go语言的包机制为封装业务逻辑模块提供了很好的机制。在设计软件时,考虑一下软件的概念组件以及它们的接口是一个好主意。对于给定组件,真正需要暴露给客户端代码的是什么?只有真正需要导出的内容才应该被导出。

进一步阅读:组织Go代码

英文:

You mentioned a couple examples. The first example (https://github.com/yohcop/openid-go/blob/master/verify.go#L11-L13):

func Verify(uri string, cache DiscoveryCache, nonceStore NonceStore) (id string, err error) {
    return verify(uri, cache, urlGetter, nonceStore)
}

You can see that the unexported verify function takes an extra urlGetter argument. This may be something that a client of this package cannot or should not provide. The exported function determines how clients of the package can/should use it; the signature of the non-exported function reflects the dependencies required to do whatever business logic verify is doing.

The second example(https://github.com/golang/oauth2/blob/master/oauth2.go#L259-L266):

func StaticTokenSource(t *Token) TokenSource {
    return staticTokenSource{t}
}

// staticTokenSource is a TokenSource that always returns the same Token.
type staticTokenSource struct {
    t *Token
}

This restricts how clients can construct the staticTokenSource: there is only one way to do it, via the StaticTokenSource constructor, and it cannot be done directly via a struct literal. This can be useful for many reasons, e.g. input validation. In this case, you want the safety of knowing that the client cannot mutate the t field on the object, and in order to do this, you leave the t field unexported. But when it's unexported, the client will not be able to construct the struct literal directly, so you must provide a constructor.

In general, it makes your code much easier to reason about when you can restrict how things are accessed, constructed, or mutated. Golang packages give you a nice mechanism to encapsulate modules of business logic. It's a good idea to think about the conceptual components of your software, and what their interfaces should be. What really needs to be exposed to client code consuming a given component? Only things that really need to be exported should be.

Further reading: Organizing Go code

huangapple
  • 本文由 发表于 2015年11月9日 08:26:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/33600714.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定