英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论