如何在
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