具有可能失败的多个步骤的struct初始化程序

时间:2018-01-08 13:48:40

标签: go

我有以下基本代码:

r1, err := OpenResource()
if err != nil { return err; }
defer r1.Close()

r2, err := OpenResource()
if err != nil { return err; }
defer r2.Close()

r3, err := OpenResource()
if err != nil { return err; }
defer r3.Close()

// Do something with r1, r2, r3
...

我想用方法DoSomething将它包装成一个结构,我将调用如下:

s, err := CreateMyStructWithR1R2R3()
if err != nil { return err }
defer s.Close()
s.DoSomethingWithR1R2R3() 

我实现这一目标的第一种方法是:

func CreateMyStructWithR1R2R3() (*MyStruct, error) {
  s := MyStruct{}

  r1, err := OpenResource()
  if err != nil { return nil, err; }
  s.r1 = r1

  r2, err := OpenResource()
  if err != nil { r1.Close(); return nil, err; }
  s.r2 = r2

  r3, err := OpenResource()
  if err != nil { r1.Close(); r2.Close(); return nil, err; }
  s.r3 = r3

  return &s
}

func (s *MyStruct) Close() {
   s.r3.Close()
   s.r2.Close()
   s.r1.Close()
}

func (s *MyStruct) DoSomethingWithR1R2R3() { /* ... */ }

但是,发生错误时Close()函数中的Create()调用会感觉很难看并且容易出错。

想到的另一种方法是:

func CreateMyStructWithR1R2R3() (*MyStruct, error) {
  s := MyStruct{}
  success := false

  r1, err := OpenResource()
  if err != nil { return nil, err; }
  defer func() { if !success { r1.Close() } }()
  s.r1 = r1

  r2, err := OpenResource()
  if err != nil { return nil, err; }
  defer func() { if !success { r2.Close() } }()
  s.r2 = r2

  r3, err := OpenResource()
  if err != nil { return nil, err; }
  defer func() { if !success { r3.Close() } }()
  s.r3 = r3

  success = true
  return &s
}

这感觉更安全,更干净,但是bool感觉很难看,当Go格式化程序出现时,这会让代码变得更长。

是否有更好的通用模式确保通过这种多阶段初始化来关闭所有资源?

2 个答案:

答案 0 :(得分:5)

您可以将Close来电推送至*MyStruct Close

func (s *MyStruct) Close() {
    if s.r3 != nil {
        s.r3.Close()
    }
    if s.r2 != nil {
        s.r2.Close()
    }
    if s.r1 != nil {
        s.r1.Close()
    }
}

并将CreateMyStructWithR1R2R3的第一个实施更新为:

func CreateMyStructWithR1R2R3() (*MyStruct, error) {
  s := MyStruct{}

  r1, err := OpenResource()
  if err != nil { s.Close(); return nil, err; }
  s.r1 = r1

  r2, err := OpenResource()
  if err != nil { s.Close(); return nil, err; }
  s.r2 = r2

  r3, err := OpenResource()
  if err != nil { s.Close(); return nil, err; }
  s.r3 = r3

  return &s
}

答案 1 :(得分:1)

如果您将err声明为返回参数,则不需要额外的success标记,因为defer将有权访问返回的错误值。

减少重复的一种方法是引入一种管理一片Closer s的类型。当在这个新类型上调用close时,它将循环遍历多个Closer,无论你有3还是300,并将它们全部关闭。说,像这样:

type Closer interface {
    Close()
}

type multiCloser struct {
    cc []Closer
}

func (mc *multiCloser) add(c Closer) {
    mc.cc = append(mc.cc, c)
}

func (mc *multiCloser) Close() {
    for _, c := range mc.cc {
        c.Close()
    }
}

有了这个,CreateMyStructWithR1R2R3的实现将如下所示:

type MyStruct struct {
    // ...
    mc *multiCloser
}

func (ms *MyStruct) Close() {
    ms.mc.Close()
}

func CreateMyStructWithR1R2R3() (ms *MyStruct, err error) {
    ms = &MyStruct{mc: &multiCloser{}}
    defer func() {
        if err != nil {
            ms.Close()
        }
    }()

    if ms.r1, err = OpenResource(); err != nil {
        return nil, err
    }
    ms.mc.add(ms.r1)

    if ms.r2, err = OpenResource(); err != nil {
        return nil, err
    }
    ms.mc.add(ms.r2)

    if ms.r3, err = OpenResource(); err != nil {
        return nil, err
    }
    ms.mc.add(ms.r3)

    return ms, nil
}