Ruby使用者对attr_accessor一定不会陌生。
class A
attr_accessor :num
end
等效于:
class A
def num
@num
end
def =(value)
@num = value
end
end
在类的定义中,attr_accessor定义了num的读写方法,只用了一行代码就生成了两个实例方法,很cool,不是嘛?。这就是Metaprogramming,用程序来编写程序。
Ruby中到底是用了什么trick能实现Metaprogramming这样cool的功能呢?
我们把attr_accessor当做一个method,那么调用attr_accessor的是self,这个self是class A,也就是说,attr_accessor是一个类方法。按照这个思路,我们可以这样写:
class A
def self.log s
attr_reader s
define_method("#{s}=") do |val|
instance_variable_set("@#{s}",val)
end
end
log "num"
end
a=A.new
a.num = 10
puts a.num
在这里,使用attr_reader来定义实例变量的读方法,define_method来定义实例变量的写方法。
使用attr_reader或者自定义的log可以用于扩展代码,我们可以将它们称为macros.
使用上面这段代码,并没有体现出Metaprogramming的威力,因为在class A中直接定义方法,要比你定义一个类方法简单明了。但是如果是这样:
class B < A
log "bnum"
end
b = B.new
b.bnum = 11
puts b.bnum # => 11
我们就可以从A中直接继承log方法,来轻松构建B中的实例读写方法的了!这里的log,是不是很类似attr_accessor呢?
到目前为止,我们通过定义类方法,来实现继承类的Metaprogramming的方法。可是,有些时候,我们并不想通过继承的方式来获得元编程的能力,如果所有类想获得某种元编程的方法只能通过继承类的方式来实现,那太麻烦了吧!这个时候,module出现了。
module A
def log s
attr_reader s
define_method("#{s}=") do |val|
instance_variable_set("@#{s}",val)
end
end
end
class B
extend A
log "bnum"
end
b=B.new
b.bnum = 11
puts b.bnum
体会一下module和extend的作用,easy,不是嘛?
如果对于一个module,级想包含其中的instance method,又想包含其中的class method,肿么办?这个时候,就要来一点trick了。
module A
module ClassMethod
def log s
attr_reader s
define_method("#{s}=") do |val|
instance_variable_set("@#{s}",val)
end
end
end
def log
puts "Just a instance method"
end
def self.included(hostclass)
hostclass.extend ClassMethod
end
end
class B
include A
log "bnum"
end
b = B.new
b.log
b.bnum = 10
puts b.bnum