我正在寻找在Go中订阅属性更改的解决方案。鉴于以下结构,我想实现一个派生属性,该属性订阅其源属性,并且只有在被读取时才会重新评估自己。如果一个或多个来源发生变化,它会因为被通知或通过检查“脏标志”(频道?)而知道这样做。 编辑:我不是在寻找一个“getter”函数,它不会缓存获取的值,而是每次读取时都会对它们进行处理。另请参阅下面添加的DeriveAndSubscribe方法,说明派生的FullName将执行的操作。
我猜这类似于一个相当典型的案例。以下示例:
type Person struct {
/FullName string // Derived, from the two below:
FirstName string // persistent
LastName string // persistent
}
对于远程订阅/提取,该概念必须是“可见的”,例如,User对象从底层Person对象派生它的详细用户信息:
type User struct {
person *Person
/FullName string // Derived from person.FullName above
}
(好吧,人们的名字不会经常变化,但示例必须简单)。
我对此的初步想法是,
拉 - 派生属性(FullName)应为“懒惰”(仅在有人正在阅读它时评估)。因此,仅在评估Fullname字符串时“拉”“任何订阅(”脏“标志/通知)似乎是最自然的,即”询问“是否发生了任何更改。
缓存 - 当导出值时,将其存储在(隐藏)字段(_fullName)中,以便在下次读取时可以重用该字符串,如果其订阅的值没有没改变。
延迟订阅 - 当有人读取FullName属性时,不仅衍生操作应该是“懒惰”,而且订阅本身也应该仅在第一次评估时放置(当有人读取属性。)
拉出的好理由而非推送似乎是订阅 在垫下时,属性可能存在也可能不存在 属性改变。如果源头没有“发送列表”那么就是 如果/当结束订阅属性/对象消失时,无需“取消注册”。并进一步;在分布式场景(不同机器上的用户和人员)中,最好只在实际明确要求数据时更新内容(也适用于订阅,只能在第一次读取FullName时放置)。
如果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 attrib更新。 (暂不考虑[用户] .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属性可以通过类似这样的方法提供(稍后可以打包成类型化的属性对象(结构)):
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
}
答案 0 :(得分: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"))
}
关于你的评论:地图查找将超过数量级的反射。
干杯!
答案 1 :(得分:0)
只需使用这些派生属性创建函数,除非基准测试显示这是一个瓶颈。在这种情况下,您仍然可以插入隐藏(小写)字段来实现任何类型的缓存。
type Person struct {
FirstName, LastName string
}
func (p *Person) FullName() string {
return p.FirstName + " " + p.LastName
}