1

我正在寻找订阅 Go 中属性更改的解决方案。鉴于以下结构,我想实现一个订阅其源属性的派生属性,并且只有在被读取时它才会重新评估自身。如果一个或多个来源发生了变化,它会因为收到通知或通过检查“脏标志”(频道?)而知道这样做。 编辑:我不是在寻找一个“getter”函数,它不会缓存获取的值,而是在每次读取时都对它们进行 fethcing)。另请参阅下面添加的 DeriveAndSubscribe 方法,说明派生的 FullName 会做什么)。

我想这类似于一个相当典型的案例。请参阅下面的示例:

type Person struct {
   /FullName  string  // Derived, from the two below:
    FirstName string  // persistent
    LastName  string  // persistent
}

对于远订阅/获取,该概念也必须是“可见的”,例如从底层 Person 对象派生其详细用户信息的 User 对象:

type User struct {
    person *Person
   /FullName string // Derived from person.FullName above
}

(好吧,人们的名字不会经常改变,但例子一定很简单)。

我自己对此的第一个想法是,

  1. - 派生属性(全名)是“懒惰的”(仅在有人阅读它/它们时评估)。因此,仅在评估 Fullname 字符串时“拉”任何订阅(“脏”标志/通知)似乎是最自然的,即“询问”是否发生了任何更改。

  2. 缓存- 派生值后,将其存储在(隐藏)字段 (_fullName) 中,以便在下一次读取时重复使用该字符串,前提是其订阅值未更改。

  3. 延迟订阅——当有人读取 FullName 属性时,不仅派生操作应该是“延迟的”,而且订阅本身也应该仅在第一次评估时放置(当有人读取属性时)。

  4. 使用 pull而不是 push 的充分理由似乎是,当底层属性发生变化时,订阅属性可能存在也可能不存在。如果源处没有“发送列表”,那么如果/当最终订阅属性/对象消失时,就不需要“取消注册”。并进一步; 在分布式场景(不同机器上的用户和人员)中,最好仅在实际明确要求数据时才更新内容(订阅也适用,只能在第一次读取 FullName 时放置)。

  5. 如果一个 goroutine(可选)可以在 CPU 不是很忙时更新(重新评估)FullName 属性,那么奢侈将是,而如果有人读取 FullName 属性,则将立即强制执行重新评估(两者都可以在一个解决方案中实现?) .

无论如何,这里是需要放置的订阅(ASCII 模型):

[Person]./FullName --> [Person].FirstName // Subscribe 1
                       [Person].LastName  // Subscribe 2

[User]./FullName --> [User].person./FullName // Subscribe 3

也就是说,共有三 (3) 个下标来保持 User.FullName 属性更新。(暂时忽略 [User].person-link)。可以使用渠道来实现这样的事情吗?如果可以,嗯……如何?

在上面插入隐藏字段的结构下方(用于缓存派生结果,直到下次源属性变得“脏”):

type Person struct {
   /FullName  string  // Derived
    _fullName string  // "cache"
    FirstName string  
    LastName  string  
}

和:

type User struct {
    person *Person
   /FullName  string  // Derived
    _fullName string  // "cache"
}

编辑: Person-FullName-attribute 可以通过这样的方法提供服务(它可以稍后打包到类型化的属性对象(结构)中):

func (p *Person) _FullName_DeriveAndSubscribe(Subscriber chan) string {
    if /* check if channel(s) is "dirty" */ {
        //
        // Keep an internal channel, and get hold of the channel, or
        // Chan of Chan(?) wich can notify us if any underlaying values change:
        //
        // _subscr = Subscriber 
        //
        // Now, update the cache 
        _fullName = FirstName + " " + LastName
    }
    return _fullName   // return the cached value
}
4

2 回答 2

4

http://play.golang.org/p/THNb3C-TLq

package main

import (
    "fmt"
)

type ChangeHandler func(interface{})

type EventedChanger interface {
    Get(name string) interface{}
    Set(name string, value interface{}) EventedChanger
    OnChange(name string, listener ChangeHandler) EventedChanger
}

type MyChanger struct {
    data      map[string]interface{}
    listeners map[string][]ChangeHandler
}

func (m *MyChanger) Get(name string) interface{} {
    val, ok := m.data[name]
    if !ok {
        return nil
    }
    return val
}

func (m *MyChanger) Set(name string, value interface{}) EventedChanger {
    m.data[name] = value
    if listeners, ok := m.listeners[name]; ok {
        for _, l := range listeners {
            l(value)
        }
    }
    return m
}

func (m *MyChanger) OnChange(name string, listener ChangeHandler) EventedChanger {
    m.listeners[name] = append(m.listeners[name], listener)
    return m
}

func NewMyChanger() *MyChanger {
    return &MyChanger{
        make(map[string]interface{}),
        make(map[string][]ChangeHandler),
    }
}

func main() {
    c := NewMyChanger()
    h := func(value interface{}) {
        c.Set("fullname", fmt.Sprint(c.Get("firstname"), c.Get("lastname")))
    }
    q := func(value interface{}) {
        fmt.Println("Full name:", value)
    }
    c.OnChange("firstname", h).OnChange("lastname", h).OnChange("fullname", q)
    c.Set("firstname", "Walter").Set("lastname", "Smith")
}

输出是:

Full name: Walter <nil>
Full name: Walter Smith

Program exited.

例如,您可以通过使其并发和/或并行执行处理程序来改进它。

编辑:

http://play.golang.org/p/msgaBXQwt_

我制作了一个更通用的版本,以符合您的懒惰和缓存要求:

package main

import (
    "fmt"
)

type Getter func(string) interface{}

type Setter func(string, interface{})

type GetSetter interface {
    Get(string) interface{}
    Set(string, interface{}) GetSetter
    RegisterGetter(string, Getter) GetSetter
    RegisterSetter(string, Setter) GetSetter
}

type LazyGetSetter struct {
    data    map[string]interface{}
    getters map[string]Getter
    setters map[string]Setter
}

func NewLazyGetSetter() *LazyGetSetter {
    return &LazyGetSetter{
        make(map[string]interface{}),
        make(map[string]Getter),
        make(map[string]Setter),
    }
}

func (l *LazyGetSetter) Get(name string) interface{} {
    if getter, ok := l.getters[name]; ok {
        return getter(name)
    }
    if val, ok := l.data[name]; ok {
        return val
    }
    return nil
}

func (l *LazyGetSetter) Set(name string, value interface{}) *LazyGetSetter {
    if setter, ok := l.setters[name]; ok {
        setter(name, value)
    } else {
        l.data[name] = value
    }
    return l
}

func (l *LazyGetSetter) RegisterGetter(name string, getter Getter) *LazyGetSetter {
    l.getters[name] = getter
    return l
}

func (l *LazyGetSetter) RegisterSetter(name string, setter Setter) *LazyGetSetter {
    l.setters[name] = setter
    return l
}

type CachedLazyGetSetter struct {
    *LazyGetSetter
    cache map[string]interface{}
}

func NewCachedLazyGetSetter() *CachedLazyGetSetter {
    return &CachedLazyGetSetter{
        NewLazyGetSetter(),
        make(map[string]interface{}),
    }
}

func (c *CachedLazyGetSetter) Cache(name string, value interface{}) *CachedLazyGetSetter {
    c.cache[name] = value
    return c
}

func (c *CachedLazyGetSetter) FetchCache(name string) interface{} {
    if val, ok := c.cache[name]; ok {
        return val
    }
    return nil
}

func main() {
    l := NewCachedLazyGetSetter()
    l.RegisterGetter("fullname", func(name string) interface{} {
        if cached := l.FetchCache(name); cached != nil {
            return cached
        }
        f := fmt.Sprintf("%s %s", l.Get("firstname"), l.Get("lastname"))
        l.Cache(name, f)
        return f
    })
    l.Set("firstname", "Walter").Set("lastname", "Smith")
    fmt.Println(l.Get("fullname"))
}

至于您的评论:地图查找将胜过反射几个数量级。

干杯!

于 2013-06-19T16:28:08.890 回答
0

除非基准测试表明这是一个瓶颈,否则只需从这些派生属性中创建函数。在这种情况下,您仍然可以插入隐藏(小写)字段来实现任何类型的缓存。

type Person struct {
    FirstName, LastName string
}

func (p *Person) FullName() string {
    return p.FirstName + " " + p.LastName
}
于 2013-06-19T14:57:57.377 回答