如何任意扩展一个”对象”?

huangapple go评论72阅读模式
英文:

How to arbitrarily extend an "object"

问题

我希望我的问题能够清楚地表达出来。我已经尽力简洁地描述了,但如果需要澄清,请提出问题。

在JavaScript中,常见的做法是通过创建新方法来修改现有对象,例如jQuery插件就是这样做的。

我在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() // 这个可以工作
p2.MoreStuff() // P2不知道pluginone的方法,所以会失败

因此,我似乎只能选择使用基于map[string]func的不太友好的API,或者最多只能有一个"插件"。

还有其他我没有考虑到的选择吗?

英文:

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

得分: 3

如果你不试图把所有责任都推给插件,你可能会实现你想要的效果。

例如,如果你希望插件彼此独立(即它们不应该知道彼此的存在),并且希望所有插件都是可选的(即你可以选择打开哪些插件),你可以选择在使用的地方创建包装类型(包装struct);该类型仅嵌入你想要使用的插件。

看看这个例子,它定义了一个基本的Thing类型,并定义了3个可选的插件,它们之间不知道彼此,只知道Thing类型。然后假设我们想要一个扩展了Plugin1Plugin3的"thing",我们可以创建一个自定义的包装器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"}
    // 假设你现在想要一个扩展了 Plugin1 和 Plugin3 的 "Thing":
    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()函数,只使用了结构体字面量。如果创建过程复杂,当然可以添加。如果Plugin1plugin1包中,它可以像这样:

func New(t *Thing) *Plugin1 {
    p := &Plugin1{t}
    // 进行其他复杂的操作
    return p
}

使用它:

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

注意3:

还要注意,不需要一个新的、命名的类型来获取一个使用Plugin1Plugin3装备的"thing"值。你可以只使用匿名结构类型和字面量,像这样:

t := &Thing{"BaseThing"}
// 假设你现在想要一个扩展了 Plugin1 和 Plugin3 的 "Thing":
t13 := struct {    *Thing; *Plugin1; *Plugin3 }{t, &Plugin1{t}, &Plugin3{t}}
英文:

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

huangapple
  • 本文由 发表于 2015年12月14日 15:44:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/34261933.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定