问题
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