How to arbitrarily extend an “object” in Go

拈花ヽ惹草 提交于 2020-01-24 12:46:08

问题


I hope my question can be made clear. I've done my best to make this concise, but please ask for clarification if it is necessary.

In JavaScript, it's common practice to have a "plugin" modify an existing object by creating new methods. jQuery plugins do this, for example.

I have the need to do something similar in Go, and am looking for the best way to do it.

The simplest to implement would simply be to store functions in a map[string]func type of data structure, then call these new "methods" with something like:

func (f *Foo) Call(name string) {
    fn := f.FuncMap[name]
    fn()
}

I can also get a much friendlier API if I use interface embedding, such as:

package thingie

type Thingie struct { ... }
type Thingier interface { ... }

func New() *Thingie { ... }
func (t *Thingie) Stuff() { ... }

And

package pluginone

type PluginOne struct { thingie.Thingier, ... }

func New(t *thingie.Thingie) *PluginOne { ... }
func (p1 *PluginOne) MoreStuff() { ... }

This works, with up to one "plugin". That is to say, I can create an object which can access all the methods in both the thingie and pluginone packages.

package main

func main() {
    t := thingie.New()
    p1 := pluginone.New(t)
    p1.Stuff()
    p1.MoreStuff()
}

The problem comes when I add a second plugin:

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

So I seem to be left with the options of an ugly API based on map[string]func, or a maximum of a single "plugin".

Are there any other alternatives I haven't considered?


回答1:


You may achieve what you want if you don't try to push everything to be the plugins' responsibility.

For example if you want your plugins to be independent from each other (that is, they shouldn't know about each other) and you want all your plugins to be optional (that is, you want to choose what plugins you want to turn on), you may choose to create the wrapper type (the wrapper struct) at the place of usage; which embeds only the plugins you want to use.

See this example, which defines a base Thing type, and defines 3 optional plugins, all which don't know about each other, only about the Thing type. Then let's say we want a "thing" extended with Plugin1 and Plugin3, we can create a custom wrapper Thing13 which embeds *Plugin1 and *Plugin3 only (besides *Thing of course).

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

Output (try it on the Go Playground):

BaseThing
Stuff, name: BaseThing (0x1040a130)
Stuff1, name: BaseThing (0x1040a130)
Stuff3, name: BaseThing (0x1040a130)

Please note that as only a pointer to Thing is embedded in each struct (*Thing), there is only one Thing value created, and it is shared across all utilized plugins (via its pointer/address), the printed pointers prove this. Also note that the Thing13 type declaration doesn't need to be in the main() function, I just did that to save some space.

Note:

Although I implemented plugins in a way that they embed *Thing, this is not a requirement. The *Thing in plugins may be a "normal" field, and everything would still work as expected.

It could look like this (the rest of the code is unchanged):

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

Output is the same, try this variant on the Go Playground.

Note #2:

For simplicity I didn't add New() functions for creating plugins, just used struct literals. If creation is complex, it can be added of course. It could look like this for Plugin1 if it is in package plugin1:

func New(t *Thing) *Plugin1 {
    p := &Plugin1{t}
    // Do other complex things
    return p
}

And using it:

t13 := &Thing13{t, plugin1.New(t), &Plugin3{t}}

Note #3:

Also note that a new, named type is not required to acquire a "thing" value armored with Plugin1 and Plugin3. You could just use an anonymous struct type and literal, like this:

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


来源:https://stackoverflow.com/questions/34261933/how-to-arbitrarily-extend-an-object-in-go

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!