【问题标题】:Method chaining in rubyruby 中的方法链接
【发布时间】:2014-04-30 11:25:35
【问题描述】:

我想构建一个 API 客户端,其接口类似于 rails 活动记录。我希望消费者能够链接方法,并且在链接最后一个方法之后,客户端会根据调用的方法请求一个 url。所以它是带有一些惰性评估的方法链接。我查看了Active Record,但这非常复杂(产卵过程等)。

这是我正在谈论的那种事情的玩具示例。在调用“get”之前,您可以将任意数量的“bar”方法链接在一起,如下所示:

puts Foo.bar.bar.get # => 'bar,bar'
puts Foo.bar.bar.bar.get # => 'bar,bar,bar'

我已经成功实现了这一点,但我宁愿不需要调用“get”方法。所以我想要的是这样的:

puts Foo.bar.bar # => 'bar,bar' 

但我目前的实现是这样的:

puts Foo.bar.bar #=> [:bar, :bar]

我曾考虑过覆盖 eachto_s 等数组方法,但我确信有更好的解决方案。

我如何链接这些方法并知道哪个是最后一个,以便我可以返回类似于get 方法中返回的字符串的内容?

这是我当前的实现:

#!/usr/bin/env ruby

class Bar
  def get(args)
    # does a request to an API and returns things but this will do for now.
    args.join(',') 
  end
end

class Foo < Array
  def self.bar
    @q = new
    @q << :bar
    @q
  end

  def bar
    self << :bar
    self
  end

  def get
    Bar.new.get(self)
  end
end

另见:Ruby Challenge - Method chaining and Lazy Evaluation

【问题讨论】:

  • 很想知道为什么使用inject 不会更简单。
  • @Denis - 在这种情况下显然会。然而,问题是如何编写支持延迟执行的方法链接的通用类。这个类的作用在这里并不重要。
  • 这里已关闭,因为不清楚我在问什么,但我有一个正确的答案。

标签: ruby lazy-evaluation method-chaining


【解决方案1】:

它与 activerecord 的工作方式是关系是数组的包装器,将任何未定义的方法委托给这个内部数组(称为target)。所以你需要从一个 BasicObject 而不是 Object 开始:

class Foo < BasicObject

然后你需要创建内部变量,你将把所有的方法委托给它:

  def method_missing(*args, &block)
    reload! unless loaded?
    @target.send(*args, &block)
  end

  def reload!
    # your logic to populate target, e.g:
    @target = @counter
    @loaded = true
  end

  def loaded?
    !!@loaded
  end

要链接方法,您的方法需要返回您的类的新实例,例如:

def initialize(counter=0)
  @counter = counter
end

def bar
  _class.new(@counter + 1)
end

private

# BasicObject does not define class method. If you want to wrap your target 
# completely (like ActiveRecord does before rails 4), you want to delegate it 
# to @target as well. Still you need to access the instance class to create 
# new instances. That's the way (if there are any suggestion how to improve it,
# please comment!)
def _class
  (class << self; self end).superclass
end

现在您可以对其进行实际检查:

p Foo.new.bar.bar.bar      #=> 3
(f = Foo.new) && nil       # '&& nil' added to prevent execution of inspect             
                           # object in the console , as it will force @target 
                           # to be loaded

f.loaded?                  #=> false
puts f                     #=> 0
f.loaded?                  #=> true

【讨论】:

  • 有效!非常感激。我添加了一个 self.bar 方法,这样我就可以调用 Foo.bar 并且我会整理我的工作并将其应用于我的要求。这是一个很大的帮助。再次感谢。
  • reload! 方法填充目标,这就是为什么你可以说这个对象是惰性求值的原因。仅当需要 target 的值并且直到 target 为空时才调用 Reload。但是,当目标被填充时 reload 不会重新填充目标(它是缓存结果),如果你想让它被重新加载,你需要手动调用它。
【解决方案2】:

一个(非常简单,也许很简单)选项是实现 to_s 方法 - 因为它用于“强制”字符串(例如在 puts 中),你可以有你的特定“这是结束链”代码。

【讨论】:

  • 是的,我想到了这个。但是如果子类覆盖了它的父类的某些方法而不是其他方法,这可能会违反直觉。
  • 同意 - 如前所述,一个粗略的选择。当我可以避免时,我会尽量避免过多的元编程。但如果你不能,@BroiSatse 答案可能更适合。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多