初始化多个资源并管理其生命周期

时间:2018-03-29 03:52:36

标签: go

假设您必须初始化一系列资源才能执行某些操作,通常需要进行一次初始化,具体取决于下一步。例如,您需要启动浏览器,打开浏览器窗口,打开选项卡,将该选项卡导航到网站。在操作结束时,您希望关闭或拆除已初始化的每个资源。

让我们看看这个天真的代码:

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()
  }
}

2 个答案:

答案 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)
}