我希望我的问题能够明确。我尽力使这个简洁,但如果有必要,请要求澄清。
在JavaScript中,通常的做法是让“插件”通过创建新方法来修改现有对象。例如jQuery plugins这样做。
我需要在Go中做类似的事情,并且正在寻找最好的方法。
最简单的实现方法是将函数存储在map[string]func
类型的数据结构中,然后用以下内容调用这些新的“方法”:
func (f *Foo) Call(name string) {
fn := f.FuncMap[name]
fn()
}
如果我使用界面嵌入,我也可以获得更友好的API,例如:
package thingie
type Thingie struct { ... }
type Thingier interface { ... }
func New() *Thingie { ... }
func (t *Thingie) Stuff() { ... }
和
package pluginone
type PluginOne struct { thingie.Thingier, ... }
func New(t *thingie.Thingie) *PluginOne { ... }
func (p1 *PluginOne) MoreStuff() { ... }
这适用于最多一个“插件”。也就是说,我可以创建一个可以访问thingie
和pluginone
包中所有方法的对象。
package main
func main() {
t := thingie.New()
p1 := pluginone.New(t)
p1.Stuff()
p1.MoreStuff()
}
当我添加第二个插件时出现问题:
t := thingie.New()
p1 := pluginone.New(t)
p2 := plugintwo.New(p2)
p2.Stuff() // This works
p2.MoreStuff() // P2 doesn't know about pluginone's methods, so this fails
所以我似乎留下了基于map[string]func
的丑陋API的选项,或者最多只有一个“插件”。
还有其他我未考虑的替代方案吗?
答案 0 :(得分:3)
如果你不试图将所有东西都当作插件的责任,那么你可以达到你想要的效果。
例如,如果您希望您的插件彼此独立(也就是说,他们不应该彼此了解),并且您希望所有插件都是可选的(也就是说,您想要选择所需的插件)要打开),您可以选择在使用地点创建包装类型(包装器struct
);它只嵌入你想要使用的插件。
请参阅此示例,该示例定义了基本Thing
类型,并定义了3个可选插件,所有插件彼此之间不了解,仅限于Thing
类型。然后,假设我们想要使用Plugin1
和Plugin3
扩展“事物”,我们可以创建一个自定义包装Thing13
,仅嵌入*Plugin1
和*Plugin3
(除此之外)当然*Thing
。
type Thing struct{ Name string }
func (t *Thing) Stuff() { fmt.Printf("Stuff, name: %s (%p)\n", t.Name, t) }
type Plugin1 struct{ *Thing }
func (p1 *Plugin1) Stuff1() { fmt.Printf("Stuff1, name: %s (%p)\n", p1.Name, p1.Thing) }
type Plugin2 struct{ *Thing }
func (p2 *Plugin2) Stuff2() { fmt.Printf("Stuff2, name: %s (%p)\n", p2.Name, p2.Thing) }
type Plugin3 struct{ *Thing }
func (p3 *Plugin3) Stuff3() { fmt.Printf("Stuff3, name: %s (%p)\n", p3.Name, p3.Thing) }
func main() {
t := &Thing{"BaseThing"}
// Let's say you now want a "Thing" extended with Plugin1 and Plugin3:
type Thing13 struct {
*Thing
*Plugin1
*Plugin3
}
t13 := &Thing13{t, &Plugin1{t}, &Plugin3{t}}
fmt.Println(t13.Name)
t13.Stuff()
t13.Stuff1()
t13.Stuff3()
}
输出(在Go Playground上尝试):
BaseThing
Stuff, name: BaseThing (0x1040a130)
Stuff1, name: BaseThing (0x1040a130)
Stuff3, name: BaseThing (0x1040a130)
请注意,由于每个结构(Thing
)中只嵌入了指向*Thing
的指针,因此只创建了一个Thing
值,并且它在所有使用的插件中共享(通过它的指针/地址),打印的指针证明了这一点。另请注意,Thing13
类型声明不需要在main()
函数中,我只是为了节省一些空间。
注意:强>
虽然我以嵌入*Thing
的方式实现了插件,但这不是必需的。插件中的*Thing
可能是“正常”字段,一切仍然可以按预期工作。
它看起来像这样(代码的其余部分保持不变):
type Plugin1 struct{ t *Thing }
func (p1 *Plugin1) Stuff1() { fmt.Printf("Stuff1, name: %s (%p)\n", p1.t.Name, p1.t) }
type Plugin2 struct{ t *Thing }
func (p2 *Plugin2) Stuff2() { fmt.Printf("Stuff2, name: %s (%p)\n", p2.t.Name, p2.t) }
type Plugin3 struct{ t *Thing }
func (p3 *Plugin3) Stuff3() { fmt.Printf("Stuff3, name: %s (%p)\n", p3.t.Name, p3.t) }
输出相同,请在Go Playground上尝试此变体。
注意#2:
为简单起见,我没有添加New()
函数来创建插件,只使用了struct
文字。如果创建很复杂,那么当然可以添加它。 Plugin1
如果它位于包plugin1
中,它可能看起来像这样:
func New(t *Thing) *Plugin1 {
p := &Plugin1{t}
// Do other complex things
return p
}
使用它:
t13 := &Thing13{t, plugin1.New(t), &Plugin3{t}}
注意#3:
另请注意,获取使用Plugin1
和Plugin3
装甲的“物品”值不需要新的命名类型。您可以使用匿名结构类型和文字,如下所示:
t := &Thing{"BaseThing"}
// Let's say you now want a "Thing" extended with Plugin1 and Plugin3:
t13 := struct { *Thing; *Plugin1; *Plugin3 }{t, &Plugin1{t}, &Plugin3{t}}