【问题标题】:How to arbitrarily extend an "object"如何任意扩展一个“对象”
【发布时间】:2015-12-14 07:44:03
【问题描述】:

我希望我的问题可以说清楚。我已尽力使此内容简洁,但如有必要,请要求澄清。

在 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,或者最多一个“插件”。

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

【问题讨论】:

    标签: inheritance plugins go embedding


    【解决方案1】:

    如果你不试图把一切都推到插件的责任上,你可能会实现你想要的。

    例如,如果您希望您的插件彼此独立(也就是说,它们不应该相互了解)并且您希望所有插件都是可选的(也就是说,您想选择您想要的插件开启),您可以选择在使用的地方创建包装器类型(包装器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}}
    

    【讨论】:

    • 一个有趣的想法。所以我仍然必须在我的顶级代码中将所有内容绑定在一起,但这种粘合剂非常少(最重要的是:为我的 API + 插件的未来用户记录相当容易)。它不像 JavaScript 那样无缝,但它是一种静态语言,所以这是意料之中的。与我之前的尝试相比,这是一个明显的改进。谢谢。
    猜你喜欢
    • 2011-09-21
    • 2011-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多