2008年1月13日星期日

attr_accessor_with_default

实现
activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb
测试
activesupport/test/core_ext/module/attr_accessor_with_default_test.rb

实现方法:
def attr_accessor_with_default(sym, default = nil, &block)
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
module_eval(<<-EVAL, __FILE__, __LINE__)
def #{sym}=(value)
class << self; attr_reader :#{sym} end
@#{sym} = value
end
EVAL
end

define_method,即定义一个方法,方法名为#{sym},另外接受一个block作为其方法体,此处为*直接*返回一个默认值。
这里就是在定义get方法。
怪就怪在后面的set方法的定义,为什么还要先写一行 class << self; attr_reader :#{sym} end 呢?
因为前面的get方法是直接返回一个默认值,并没有生成一个名为@#{sym}的实例变量,所以这一行的作用是用Ruby提供的attr_reader方法,来重新生成一个get方法,这个新的get方法里,自然就有名为@#{sym}的实例变量了。
然后再是标准的set方法体:@#{sym} = value
三个方法的生成过程为:
由 define_method(sym, block_given? ? block : Proc.new { default }) 生成:
def foo
default
end
由class << self; attr_reader :#{sym} end 生成:
def foo
@foo
end
def foo=(value)
@foo = value
end
所以一旦调用了set方法,前面的get方法被后面的get方法覆盖。

为了证实,可以修改实现文件和测试文件:
def attr_accessor_with_default(sym, default = nil, &block)
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
module_eval(<<-EVAL, __FILE__, __LINE__)
def #{sym}=(value)
class << self; attr_reader :#{sym} end
@#{sym} = value
# @#{sym} = value # 注释掉该行,使得set方法不赋值,但仍然生成一个新的get方法,并生成名为@#{sym}的实例变量
end
EVAL
end
def test_default_arg
@target.attr_accessor_with_default :foo, :bar
p @instance.foo # 执行set方法前,调用get方法
assert_equal(:bar, @instance.foo)
@instance.foo = 'foo' # 此处赋值为'foo',而非原来的nil
p @instance.foo # 执行set方法后,再调用get方法
# assert_nil(@instance.foo) # 注释掉该行,以免因为我们的修改而使测试出错
end

运行测试,打印出结果:

...:bar
nil...


也就是说,第一次调用get方法,是按照预想的返回一个默认值 :bar,此时还是旧的get方法。第二次调用时,返回的是新的get方法里的实例变量@foo,而@foo并未被赋值,所以返回值是nil(前一行的@instance.foo = 'foo'并没有起到赋值的作用,因为我们在实现文件里已经把相应的行注释掉了)

没有评论: