假设您必须初始化一系列资源才能执行某些操作,通常需要进行一次初始化,具体取决于下一步。例如,您需要启动浏览器,打开浏览器窗口,打开选项卡,将该选项卡导航到网站。在操作结束时,您希望关闭或拆除已初始化的每个资源。
让我们看看这个天真的代码:
func main() {
window, err := NewWindow()
if err != nil {
panic(err)
}
defer window.Close()
tab, err := NewTab(window)
if err != nil {
panic(err)
}
defer tab.Close()
NavigateToSite(tab)
}
(当然,这段代码很简单,所以有人可能会问为什么要重构它,所以请记住它的例子,并且实际的初始化链可能会更长,更复杂。)
假设我想分解初始化,注意到我的代码中的实际逻辑根本不需要window
。什么是惯用的方法呢?到目前为止,我可以想到:
func main() {
rs, err := NewMyResource()
if err != nil {
panic(err)
}
defer rs.Close()
NavigateToSite(rs.Tab)
}
struct MyResource {
Window *window;
Tab *tab;
}
func NewMyResource() (*MyResource, error) {
rs := &MyResource{}
window, err := CreateWindow()
if err != nil {
rs.Close()
return nil, err
}
rs.Window = window
tab, err := CreateTab()
if err != nil {
rs.Close()
return nil, err
}
rs.Tab := tab
return rs, nil
}
func (rs MyResource) Close() {
if rs.Window != nil {
rs.Window.Close()
}
if rs.Tab != nil {
rs.Tab.Close()
}
}
答案 0 :(得分:1)
一种可能的替代方案(不一定更好,取决于上下文)可能是返回一个闭包:
func NewMyResource() (tab Tab, closer func(), err error) {
var window Window
window, err = NewWindow()
if err != nil {
return
}
tab, err = NewTab(window)
if err != nil {
return
}
closer = func() {
tab.Close()
window.Close()
}
return
}
使用类似的东西:
tab, cl, err := NewMyResource()
if err != nil {
panic(err)
}
defer cl()
我通常会使用基于结构的解决方案,但有时新类型是过度的,返回函数会更容易。
答案 1 :(得分:0)
一个相当复杂的解决方案(优雅留给你判断)涉及一个通用的辅助函数:
func ContextCloser(ctx context.Context, closer io.Closer) {
go func() {
for {
select {
case <-ctx.Done():
closer.Close()
return
}
}
}()
}
此帮助程序函数允许使用杠杆上下文进行资源管理,有点类似于分配/发布池。让我告诉你如何:
struct MyResource {
ContextCancel context.CancelFunc
// NOTE: we no longer keep `window` etc. for interim variables
// that our caller doesn't really want and we only kept
// so we could close them --
// ContextCloser will take care of it!
Tab *tab;
}
func NewMyResource(ctx context.Content) (*MyResource, error) {
var err error
ctx, ctxCancel := context.WithCancel(ctx)
// This ensures resources are closed if this function fails
defer func() {
if err != nil {
ctxCancel()
}
}()
// We need to create a window in order to create a tab,
// but we don't need to return it
window, err := CreateWindow()
if err != nil {
return nil, err
}
ContextCloser(ctx, window)
tab, err := CreateTab()
if err != nil {
return nil, err
}
ContextCloser(ctx, tab)
return &MyResource{ContextCancel: ctxCancel, Tab: tab}, nil
}
最后,为了完整起见,让我们来说明它是如何被称为的:
func main() {
// (you can use this context for more than just NewMyResource)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rs, err := NewMyResource(ctx)
if err != nil {
panic(err)
}
NavigateToSite(rs.Tab)
}