英文:
Should I avoid package singletons in golang?
问题
目前,我有一个名为store
的包,其内容如下:
package store
var (
db *Database
)
func Open(url string) error {
// 打开数据库连接
}
func FindAll(model interface{}) error {
// 返回所有条目
}
func Close() {
// 关闭数据库连接
}
在main.go
中执行store.Open
之后,我可以在其他包中使用store.FindAll
。
然而,据我所见,大多数包更倾向于提供一个需要自己初始化的结构体。只有少数情况下才会使用这种全局方法。
这种方法的缺点是什么,我应该避免使用吗?
英文:
at the moment I have a package store
with following content:
package store
var (
db *Database
)
func Open(url string) error {
// open db connection
}
func FindAll(model interface{}) error {
// return all entries
}
func Close() {
// close db connection
}
This allows me to use store.FindAll
from other packages after I have done store.Open
in main.go
.
However as I saw so far most packages prefer to provide a struct you need to initialize yourself. There are only few cases where this global approach is used.
What are downsides of this approach and should I avoid it?
答案1
得分: 5
- 你不能同时实例化两个存储连接。
- 你不能轻松地使用方便的工具(如gomock)在依赖代码的单元测试中模拟存储。
英文:
- You can't instantiate connections to 2 storages at once.
- You can't easily mock out storage in unit tests of dependent code using convenient tools like gomock.
答案2
得分: 4
标准的http
包有一个ServerMux
用于通用用途,但也有一个名为DefaultServerMux
的默认实例(http://golang.org/pkg/net/http/#pkg-variables)以方便使用。因此,当您调用http.HandleFunc
时,它会在默认的mux上创建处理程序。您可以在log
和许多其他包中找到相同的方法。这本质上是您的“单例”方法。
然而,我认为在您的用例中遵循这种模式不是一个好主意,因为用户需要调用Open
而不管默认数据库是什么。因此,使用默认实例实际上并不会真正帮助,反而会使它不太方便:
d := store.Open(...)
defer d.Close()
d.FindAll(...)
相比之下,这种写法更容易编写和阅读:
store.Open(...)
defer store.Close()
store.FindAll(...)
而且,还存在语义问题:如果有人调用两次Open
会发生什么:
store.Open(...)
defer store.Close()
...
store.Open(...)
store.FindAll(...) // 这是指哪个数据库?
英文:
The standard http
package has a ServerMux
for generic usecases and but also has one default instance of ServerMux
called DefaultServerMux
(http://golang.org/pkg/net/http/#pkg-variables) for convenience. So that when you call http.HandleFunc
it creates the handler on the default mux. You can find the same approach used in log
and many other packages. This is essentially your "singleton" approach.
However, I don't think it's a good idea to follow that pattern in your use case, since the users need to call Open
regardless of the default database. And, because of that, using a default instance would not really help and instead would actually make it less convenient:
d := store.Open(...)
defer d.Close()
d.FindAll(...)
is much easier to both write and read than:
store.Open(...)
defer store.Close()
store.FindAll(...)
And, also there are semantic problems: what should happen if someone calls Open
twice:
store.Open(...)
defer store.Close()
...
store.Open(...)
store.FindAll(...) // Which db is this referring to?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论