如何在
ruby中创建一个Object,该对象将在类似于nil的逻辑表达式中被评估为false
我的意图是在其他对象的某个地方下一个值通常为零,但允许所有的调用继续 – 返回我的零对象而不是零本身的其他对象的嵌套调用.对象将返回自身,以响应任何收到的消息,它不知道如何处理,我预计我将需要实现一些覆盖方法,如nil?
例如:
fizz.buzz.foo.bar
如果fizz的嗡嗡声属性不可用,我会返回我的零对象,这样就可以接受呼叫来阻止返回自身.最终,上述声明应该评估为假.
编辑:
根据下面所有的很好的答案,我想出了以下几点:
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 }
解决方法
这是一个很长的答案,一系列的想法和代码示例如何解决问题.
尝试
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)
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,&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,&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],&blk) if @me else @me = @me.send(m,&blk) if @me # Otherwise call method on `@me` and self # store result then return mock. end end end
解释发生了什么:当您调用一个下划线的方法时,您触发模拟模式,_meth的结果将自动包装在Mock对象中.每当你调用这个模拟的方法,它会检查它是否不持有一个零,然后将你的方法转发到该对象(这里存储在@me变量中).然后,模拟器将使用函数调用的结果替换原始对象.当你调用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