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

Rem*_*mko 1 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
...
Run Code Online (Sandbox Code Playgroud)

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

s, err := CreateMyStructWithR1R2R3()
if err != nil { return err }
defer s.Close()
s.DoSomethingWithR1R2R3() 
Run Code Online (Sandbox Code Playgroud)

我实现这个的第一个方法是:

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() { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

但是,发生错误时函数中的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
}
Run Code Online (Sandbox Code Playgroud)

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

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

abh*_*ink 5

你可以把Close电话推到*MyStruct's 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

并更新第一个实现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
}
Run Code Online (Sandbox Code Playgroud)