duanjia7912 2015-12-14 07:44
浏览 21

如何在Go中任意扩展“对象”

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条回答 默认 最新

  • dongmeng2687 2015-12-14 08:08
    关注

    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)
    ", t.Name, t) }
    
    type 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)
    ", 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"}
        // 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)
    ", 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) }
    

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

报告相同问题?

悬赏问题

  • ¥15 拟通过pc下指令到安卓系统,如果追求响应速度,尽可能无延迟,是不是用安卓模拟器会优于实体的安卓手机?如果是,可以快多少毫秒?
  • ¥20 神经网络Sequential name=sequential, built=False
  • ¥16 Qphython 用xlrd读取excel报错
  • ¥15 单片机学习顺序问题!!
  • ¥15 ikuai客户端多拨vpn,重启总是有个别重拨不上
  • ¥20 关于#anlogic#sdram#的问题,如何解决?(关键词-performance)
  • ¥15 相敏解调 matlab
  • ¥15 求lingo代码和思路
  • ¥15 公交车和无人机协同运输
  • ¥15 stm32代码移植没反应