如何在Go中任意扩展“对象”

时间:2015-12-14 07:44:03

标签: inheritance plugins go embedding

我希望我的问题能够明确。我尽力使这个简洁,但如果有必要,请要求澄清。

在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() { ... }

这适用于最多一个“插件”。也就是说,我可以创建一个可以访问thingiepluginone包中所有方法的对象。

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的选项,或者最多只有一个“插件”。

还有其他我未考虑的替代方案吗?

1 个答案:

答案 0 :(得分:3)

如果你不试图将所有东西都当作插件的责任,那么你可以达到你想要的效果。

例如,如果您希望您的插件彼此独立(也就是说,他们不应该彼此了解),并且您希望所有插件都是可选的(也就是说,您想要选择所需的插件)要打开),您可以选择在使用地点创建包装类型(包装器struct);它只嵌入你想要使用的插件。

请参阅此示例,该示例定义了基本Thing类型,并定义了3个可选插件,所有插件彼此之间不了解,仅限于Thing类型。然后,假设我们想要使用Plugin1Plugin3扩展“事物”,我们可以创建一个自定义包装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:

另请注意,获取使用Plugin1Plugin3装甲的“物品”值不需要新的命名类型。您可以使用匿名结构类型和文字,如下所示:

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}}