如何随意地扩展“对象”在Go [英] How to arbitrarily extend an "object" in Go
问题描述
在JavaScript中,通常会有一个插件修改现有的对象通过创建新的方法。例如 jQuery插件就是这样做的。
我需要在Go中做类似的事情,并且正在寻找最好的方法。
最简单的实现就是将函数存储在 map [string] func
类型的数据结构,然后用如下方法调用这些新的方法:
func(f * Foo)Call(name string){
fn:= f.FuncMap [name]
fn()
}
如果我使用接口嵌入,我还可以获得更友好的API,例如:
包thingie
类型Thingie struct {...}
类型Thingier接口{...}
func New()* Thingie {...}
func(t * Thingie)Stuff(){...}
和
包pluginone
类型PluginOne struct {thingie。 Thingier,...}
func新(t * thingie.Thingie)* PluginOn e {...}
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不知道pluginone的方法,所以这个失败
似乎留下了基于 map [string] func
的丑陋API的选项,或者最多只有一个插件。
是否还有其他替代方案我没有考虑过?
你不会试图推动所有的事情成为插件的负责人例如,如果你想让你的插件彼此独立(也就是说,他们不应该彼此了解),并且你希望所有的插件都可以是可选的(也就是说,您想选择要打开的插件),您可以选择在使用地点创建包装器类型(包装器 struct
) ;它只嵌入你想要使用的插件。
看到这个例子,它定义了一个基 Thing
类型,并且定义了3个可选的插件,所有这些插件都彼此不知道,只关于 Thing
类型。然后假设我们想要一个用 Plugin1
和 Plugin3
扩展的thing,我们可以创建一个自定义的包装器 Thing13
,它只嵌入 * Plugin1
和 * Plugin3
(除<$ c
类型Thing struct {Name string}
func(t * Thing)Stuff(){fmt.Printf(Stuff,name:%s(%p)\\\
,t.Name,t)}
类型Plugin1 struct {* Thing}
func(p1 * Plugin1)Stuff1(){fmt.Printf(Stuff1,name:%s(%p)\\\
,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)\\\
,p3.Name,p3.Thing)}
func main(){
t:=& Thing {BaseThing}
//假设您现在想要使用Plugin1和Plugin3扩展的Thing:
t ype 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
$ p $请注意,因为只有指向
Stuff,name:BaseThing(0x1040a130)
Stuff1,name:BaseThing(0x1040a130)
Stuff3,name:BaseThing(0x1040a130)
Thing
的指针嵌入在每个结构中(* Thing
),创建的只有一个
Thing
值,并且它在所有使用的插件中(通过它的指针/地址)共享,打印的指针证明了这一点。还要注意,Thing13
类型声明不需要在main()
函数中,我只是这么做的以节省一些空间。
注意:
他们嵌入的方式
* Thing
,这不是必需的。插件中的* Thing
可能是一个普通字段,并且所有内容都可以按预期工作。
它可能看起来像这样(其余代码没有变化):
pre $type Plugin1 struct {t * Thing}
func(p1 * Plugin1)Stuff1(){fmt.Printf(Stuff1,name:%s(%p)\\\
,p1.t.Name,p1.t)}
type Plugin2 struct {t * Thing}
func(p2 * Plugin2)Stuff2(){fmt.Printf(Stuff2,name:%s(%p)\\\
,p2 .t.Name,p2.t)}
type Plugin3 struct {t * Thing}
func(p3 * Plugin3)Stuff3(){fmt.Printf(Stuff3 ,name:%s(%p)\\\
,p3.t.Name,p3.t)}
输出结果相同,请在 Go Playground 上尝试此变体。
注意#2:
为了简单起见,我没有添加 New()
创建插件的函数,只用 struct
文字。如果创作很复杂,当然可以添加。如果包含 plugin1
: $ b>,它可能看起来像 Plugin1
$ b
func New(t * Thing)* Plugin1 {
p:=& Plugin1 {t}
//做其他复杂的事情
返回p
}
并使用它:
t13:=& Thing13 {t,plugin1.New(t),& Plugin3 {t}}
pre>
注意#3:
另外请注意,新的命名类型并不需要获取装有Plugin1
和Plugin3
的东西值。您可以使用匿名结构类型和文字,如下所示:
t:=& Thing {BaseThing}
//假设你现在想用一个Thing扩展Plugin1和Plugin3:
t13:= struct {* Thing; * Plugin1; * Plugin3} {t,& Plugin1 {t},& Plugin3 {t}}
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
andpluginone
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?
解决方案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 theThing
type. Then let's say we want a "thing" extended withPlugin1
andPlugin3
, we can create a custom wrapperThing13
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 oneThing
value created, and it is shared across all utilized plugins (via its pointer/address), the printed pointers prove this. Also note that theThing13
type declaration doesn't need to be in themain()
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 usedstruct
literals. If creation is complex, it can be added of course. It could look like this forPlugin1
if it is in packageplugin1
: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
andPlugin3
. 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}}
这篇关于如何随意地扩展“对象”在Go的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!