在golang中,我应该避免使用包单例吗?

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

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

  1. 你不能同时实例化两个存储连接。
  2. 你不能轻松地使用方便的工具(如gomock)在依赖代码的单元测试中模拟存储。
英文:
  1. You can't instantiate connections to 2 storages at once.
  2. 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?

huangapple
  • 本文由 发表于 2014年7月5日 14:08:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/24583542.html
匿名

发表评论

匿名网友

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

确定