我有以下基本代码:
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格式化程序出现时,这会让代码变得更长。
是否有更好的通用模式确保通过这种多阶段初始化来关闭所有资源?
答案 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
}