如何随意地扩展“对象”在Go [英] How to arbitrarily extend an "object" in Go

查看:135
本文介绍了如何随意地扩展“对象”在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 
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 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?

解决方案

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

这篇关于如何随意地扩展“对象”在Go的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆