Inheritance 我如何组织这段Go代码,将方法从嵌入式类型重新定义为更少冗余和更易于维护?
我在其中声明了一个类型Inheritance 我如何组织这段Go代码,将方法从嵌入式类型重新定义为更少冗余和更易于维护?,inheritance,go,methods,embedding,organization,Inheritance,Go,Methods,Embedding,Organization,我在其中声明了一个类型foo,其中包含一些相互调用的方法(比如:foo.get,由foo.double和foo.toString调用) 我有另一种类型,bar,它嵌入了foo,并重新定义了get。我被迫重新定义bar上的double和toString,这样他们就可以看到bar.get(而不仅仅是foo.get),但这些函数的主体基本上与原始函数相同 是否有更好的方法来组织此代码,以避免冗余,同时仍有bar实现与foo相同的接口 注: 上述组织的准则适用;这很难维护,因为当我在嵌入foo的类型上
foo
,其中包含一些相互调用的方法(比如:foo.get
,由foo.double
和foo.toString
调用)
我有另一种类型,bar
,它嵌入了foo
,并重新定义了get
。我被迫重新定义bar
上的double
和toString
,这样他们就可以看到bar.get
(而不仅仅是foo.get
),但这些函数的主体基本上与原始函数相同
是否有更好的方法来组织此代码,以避免冗余,同时仍有bar
实现与foo
相同的接口
注:
- 上述组织的准则适用;这很难维护,因为当我在嵌入
的类型上重新定义最初在foo
上声明的方法时,我必须仔细检查foo
上有哪些其他方法调用它,并确保重新定义所有这些方法。事实证明,很容易不小心漏掉一个foo
- 在基于此的实际项目中,有近十几种类型嵌入了“
”,每种类型上都有相似数量的相互连接的方法foo
- 我可以向foo添加一个接口值成员,该成员包含一个指向嵌入它的最外层封闭结构的指针,并让
调用foo.double
,而不是f.outermost.get()
,但这似乎有点浪费,意味着f.get()
的零值无效。(另外,仅当嵌入类型为结构时才可能。)foo
double()
和toString()
方法(在Go中应称为String()
),而不是get()
方法,因为它的实现随着类型的变化而变化
所以基本上你应该重构一点。您的foo
类型应该具有/获取提供get()
方法的值。您可以使用getter
界面捕捉到这一点:
type getter interface {
get() int
}
以及foo
实现:
type foo struct {
g getter
}
func (f foo) double() int {
return f.g.get() * 2
}
func (f foo) toString() string {
return fmt.Sprintf("%d", f.g.get())
}
以及嵌入foo
的bar
类型,只提供“缺少的”get()
:
用法示例:
b := bar{}
b.foo = foo{g: b}
fmt.Println(b.double())
fmt.Println(b.toString())
输出与预期一致(请在上尝试):
用一个简单的函数值
使用上面的getter
接口很好,因为它在将来需要添加其他方法时提供了灵活性
如果情况并非如此,并且您只需要一个函数,那么您可以省去接口而只使用函数值
这就是它的样子:
type foo struct {
get func() int
}
func (f foo) double() int {
return f.get() * 2
}
func (f foo) toString() string {
return fmt.Sprintf("%d", f.get())
}
type bar struct {
foo
}
func (b bar) get() int {
return 69
}
使用它:
b := bar{}
b.foo = foo{get: b.get}
fmt.Println(b.double())
fmt.Println(b.toString())
输出是相同的。试穿一下
请注意,虽然我们使用了bar.get()
方法(b.get
),但并不要求get()
应该是一种方法。它可以是一个普通函数值,甚至是一个函数值,例如:
b := bar{foo: foo{get: func() int { return 69 }}}
fmt.Println(b.double())
fmt.Println(b.toString())
在上试试这个。是的:我希望我的基类更脆弱一些……我喜欢这种方法;这无疑使代码更易于维护。遗憾的是,它的代价是使
foo
和bar
比接口变量的大小更大(如果有很多实例,这会很昂贵),并使零值无效-/接口变量的大小只有几个字节;如果内存消耗是您最关心的问题,那么您应该以牺牲设计为代价来关注它。但需要很多很多实例才能产生重大影响,因为接口值只会增加两个字的开销(64位系统中为16字节)。据推测,到目前为止,您的数据结构使这一点黯然失色。噢:我必须收回我以前的评论(但我不会删除它,因为Adrain的回复将缺少上下文):我现在看到额外的成本只是指针的大小,而不是接口值。我用“tsk”来表示我自己的不专心阅读。@Adrian:在这种情况下,我实际的foo
对象只有40个字节(其中7个是对齐填充),因此再加上16个字节并不重要。幸运的是,我想出了一个聪明的方法来重新安排事情,添加了一个接口值,但在最常见的情况下,在大小上没有净变化。
b := bar{}
b.foo = foo{get: b.get}
fmt.Println(b.double())
fmt.Println(b.toString())
b := bar{foo: foo{get: func() int { return 69 }}}
fmt.Println(b.double())
fmt.Println(b.toString())