ruby - Ruby像nil的对象

如何在ruby中创建一个对象,该对象在类似于nil的逻辑表达式中被求值为false?
我的目的是在其他对象上启用嵌套调用,在链的中间位置,值通常是nil,但允许所有调用继续-返回我的nil-like对象,而不是nil本身。对象将返回自身,以响应它不知道如何处理的任何接收到的消息,我预计我将需要实现一些重写方法,如nil?
例如:

fizz.buzz.foo.bar

如果buzzfizz属性不可用,我将返回类似nil的对象,它将一直接受调用,直到bar返回自身。最后,上面的陈述应该被评估为错误的。
编辑:
根据下面所有的答案,我得出了以下结论:
class NilClass
  attr_accessor :forgiving
  def method_missing(name, *args, &block)
    return self if @forgiving
    super
  end
  def forgive
    @forgiving = true
    yield if block_given?
    @forgiving = false
  end
end

这允许一些卑鄙的伎俩,比如:
nil.forgiving {
    hash = {}
    value = hash[:key].i.dont.care.that.you.dont.exist
    if value.nil?
        # great, we found out without checking all its parents too
    else
        # got the value without checking its parents, yaldi
    end
}

显然,您可以将这个块透明地包装在一些函数call/class/module/where中。


最佳答案:

这是一个相当长的答案,包含了一系列关于如何解决问题的想法和代码示例。
try
rails有一个try method让我们像那样编程。这就是它的实现方式:

class Object
  def try(*args, &b)
    __send__(*a, &b)
  end
end

class NilClass        # NilClass is the class of the nil singleton object
  def try(*args)
    nil
  end
end

你可以这样编程:
fizz.try(:buzz).try(:foo).try(:bar)

可以想象,您可以对其进行修改,使其工作方式略有不同,以支持更优雅的api:
class Object
  def try(*args)
    if args.length > 0
      method = args.shift         # get the first method
      __send__(method).try(*args) # Call `try` recursively on the result method
    else
      self                        # No more methods in chain return result
    end
  end
end
# And keep NilClass same as above

然后你可以:
fizz.try(:buzz, :foo, :bar)

andand
andand使用了一种更邪恶的技术,破解了无法直接实例化nilclass子类的事实:
class Object
  def andand
    if self
      self
    else               # this branch is chosen if `self.nil? or self == false`
      Mock.new(self)   # might want to modify if you have useful methods on false
    end
  end
end

class Mock < BasicObject
  def initialize(me)
    super()
    @me = me
  end
  def method_missing(*args)  # if any method is called return the original object
    @me
  end
end

这允许您以这种方式编程:
fizz.andand.buzz.andand.foo.andand.bar

再加上一些花哨的重写
您可以再次扩展此技术:
class Object
  def method_missing(m, *args, &blk)        # `m` is the name of the method
    if m[0] == '_' and respond_to? m[1..-1] # if it starts with '_' and the object
      Mock.new(self.send(m[1..-1]))         # responds to the rest wrap it.
    else                                    # otherwise throw exception or use
      super                                 # object specific method_missing
    end
  end
end

class Mock < BasicObject
  def initialize(me)
    super()
    @me = me
  end
  def method_missing(m, *args, &blk)
    if m[-1] == '_'  # If method ends with '_'
      # If @me isn't nil call m without final '_' and return its result.
      # If @me is nil then return `nil`.
      @me.send(m[0...-1], *args, &blk) if @me 
    else 
      @me = @me.send(m, *args, &blk) if @me # Otherwise call method on `@me` and
      self                                  # store result then return mock.
    end
  end
end

要解释发生了什么:当调用带下划线的方法时,触发模拟模式,_meth的结果将自动包装在一个Mock对象中。无论何时调用该mock上的方法,它都会检查是否不包含nil,然后将方法转发给该对象(此处存储在@me变量中)。然后,mock用函数调用的结果替换原始对象。当您调用meth_时,它结束模拟模式并返回meth的实际返回值。
这允许这样的api(我使用了下划线,但您可以使用任何东西):
fizz._buzz.foo.bum.yum.bar_

残暴的猴子修补方法
这确实很糟糕,但它允许一个优雅的api,而且不一定会在整个应用程序中导致错误报告:
class NilClass
  attr_accessor :complain
  def method_missing(*args)
    if @complain
      super
    else
      self
    end
  end
end
nil.complain = true

像这样使用:
nil.complain = false
fizz.buzz.foo.bar
nil.complain = true

译文:来源   文章分类: ruby metaprogramming null-object-pattern

相关文章:

ruby-on-rails - 有没有办法检查Ruby变量是否包含二进制数据?

jquery - 机架抛出EOFError(错误的内容主体)

ruby - 无法加载Nokogiri

ruby - Ruby:为什么在这里会收到“正则表达式字面量正常”的警告?

ruby - 如何在Mac上为Ruby添加根CA证书

ruby - 组织Ruby测试文件夹结构的最佳实践是什么?

ruby - 使用remove_instance_variable vs.设置为nil

ruby - 使用包含和扩展的Ruby模块mixin-如何工作?

ruby-on-rails - rails 3.1 db:test:prepare和“待迁移”问题

arrays - 如何在ARGV中将数组作为参数传递