草庐IT

go - 惰性评估的订阅者

coder 2023-06-29 原文

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

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

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

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

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

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

我自己的第一个想法是,

  1. Pull - 派生属性 (FullName) 是“惰性的”(仅在有人阅读时才评估)。因此,仅在评估全名字符串时“拉取”任何订阅(“脏”标志/通知)似乎是最自然的做法,也就是说,“询问”是否发生了任何更改。

  2. 缓存 - 派生值后,将其存储在(隐藏)字段 (_fullName) 中,以便在下一次读取时可以重复使用该字符串(如果其订阅值尚未获得) '改变了。

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

  4. 拉动而不是推的好理由似乎是订阅 底层可能存在也可能不存在属性 属性变化。如果源头上没有“发送列表”,那么就会有 如果/当结束订阅属性/对象消失时,也无需“注销”。并进一步;在分布式场景中(用户和人员在不同的机器上),最好仅在实际明确要求数据时才更新内容(这也适用于订阅,只能在第一次读取 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)。可以使用 channel 来实现这样的事情吗?如果可以,嗯……怎么做?

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

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
}

最佳答案

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

关于您的评论: map 查找的性能将优于反射几个数量级。

干杯!

关于go - 惰性评估的订阅者,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17193513/

有关go - 惰性评估的订阅者的更多相关文章

  1. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

  2. ruby - 在 Ruby 中的另一个上下文中评估潜在的相对 URI - 2

    我在Ruby程序中有两个URI。一个肯定是绝对URI,另一个可能是绝对URI或相对URI。我想在第一个的上下文中将第二个转换为绝对URI,所以如果第一个是http://pupeno.com/blog第二个是/about,结果应该是http://pupeno.com/about.有什么想法吗? 最佳答案 Ruby的内置URI和Addressablegem,做这个简短的工作。我更喜欢Addressable,因为它功能更全面,但URI是内置的。require'uri'URI.join('http://pupeno.com/blog','/

  3. ruby - block 内的实例评估 - 2

    我有一个Builder类,可让您添加到其中一个实例变量:classBuilderdefinitialize@lines=[]enddeflinesblock_given??yield(self):@linesenddefadd_line(text)@lines现在,我该如何改变它my_builder=Builder.newmy_builder.lines{|b|b.add_line"foo"b.add_line"bar"}pmy_builder.lines#=>["foo","bar"]进入这个?my_builder=Builder.newmy_builder.lines{add_li

  4. ruby - 使用 autoload 与 ruby​​ 中的 require 进行惰性评估? - 2

    在我的代码中,我使用自动加载进行惰性评估,这样我可以更快地加载程序并在需要时加载文件,我没有看到很多人使用它,但在Thin项目中我注意到自动加载已被广泛使用,反正只是想知道使用它是否有任何风险。 最佳答案 autoload是notthreadsafe并将在未来的Ruby版本中弃用。这是proofbyMatz(ruby的创造者)。 关于ruby-使用autoload与ruby​​中的require进行惰性评估?,我们在StackOverflow上找到一个类似的问题:

  5. ruby-on-rails - Textmate 'Go to symbol' 相当于 Vim - 2

    在Railcasts上,我注意到一个非常有趣的功能“转到符号”窗口。它像Command-T一样工作,但显示当前文件中可用的类和方法。如何在vim中获取它? 最佳答案 尝试:helptags有各种程序和脚本可以生成标记文件。此外,标记文件格式非常简单,因此很容易将sed(1)或类似的脚本组合在一起,无论您使用何种语言,它们都可以生成标记文件。轻松获取标记文件(除了下载生成器之外)的关键在于格式化样式而不是实际解析语法。 关于ruby-on-rails-Textmate'Gotosymbol

  6. ruby-on-rails - Ruby 挑战 - 方法链和惰性求值 - 2

    看完文章http://jeffkreeftmeijer.com/2011/method-chaining-and-lazy-evaluation-in-ruby/,我开始寻找更好的方法链和惰性求值解决方案。我想我已经用以下五个规范概括了核心问题;谁能让他们全部通过?任何事情都可以:子类化、委托(delegate)、元编程,但不鼓励后者。最好将依赖性保持在最低限度:require'rspec'classFoo#EpiccodehereenddescribeFoodoit'shouldreturnanarraycorrespondingtothereverseofthemethodchai

  7. ruby-on-rails - 无法使用 Stripe 保存或取消订阅 - 2

    将stripe的API与RubyonRails结合使用我无法保存订阅。我能够检索、更新和保存客户对象:customer=Stripe::Customer.retrieve(some_customer_id)#thisworkscustomer.save#thisworks我还可以检索订阅:subscription=customer.subscriptions.retrieve("some_subscription_id")#这个有效但是,在尝试保存订阅时:subscription.save#这不起作用我不断得到这个:NoMethodError:undefinedmethod`save'

  8. ruby - 帮助评估构建工具 - 2

    我已经熟悉并使用Ant&Maven,此时我想扩展到另一个工具,我在“Buildr”和“Gradle”之间做出决定。我非常感谢那些使用过其中一种或两种工具的人的见解/反馈,因为坦率地说,在这一点上,对我来说唯一真正的区别似乎是ruby​​与groovy(我对两者都感到满意并喜欢)。我也希望能回答以下问题:我知道Buildr允许下载和提取不在maven类型存储库中的依赖项,Gradle是否提供相同的功能?Buildr/Gradle能否用于构建其他语言的源代码——即groovy、ruby、actionscript/flex、c系列等?Buildr/Gradle与Hudson或Jenkins的

  9. ruby-on-rails - Ruby 类评估,validates_inclusion_of 与动态数据 - 2

    如果我有如下的ActiveRecord模型classFooself.allowed_typesdefself.allowed_types#somecodethatreturnsanenumerableendend这不起作用,因为在评估验证时尚未定义allowed_types方法。我能想到的所有修复基本上都是围绕将方法定义移到验证之上,以便在需要时可用。我明白这可能更像是一个编码风格问题(我希望我的所有验证都在模型顶部,方法在底部)但我觉得应该有某种解决方案,可能涉及初始模型加载的惰性评估?我想做的事有可能吗?我应该只在验证之上定义方法,还是有更好的验证解决方案来实现我想要的。

  10. ruby-on-rails - 在 Rails 中是否有比 Observers 更直接的方式来执行发布/订阅模式? - 2

    我有一个模型依赖于一个单独的、联合的模型。classMagazine图像是多态的,可以附加到许多对象(页面和文章),而不仅仅是杂志。杂志需要在相关图像发生任何变化时自行更新该杂志还保存了一张自己的截图,可用于宣传:classMagazine现在如果图像发生变化,杂志也需要更新其截图。所以杂志真的需要知道图片什么时候出了问题。所以我们可以天真地直接从封面图片触发屏幕截图更新classImage...但是图片不应该代表杂志做事然而,图片可以用于许多不同的对象,实际上不应该对杂志进行特定的操作,因为这不是图片的责任。该图像也可能附加到页面或文章,并且不需要为它们做各种事情。“正常”的rail

随机推荐