Ruby学习之元编程
Kernel#evel()方法
和Object#instance_evel()、Module#class_evel()方法类似,evel()方法也是一个内核方法,Object#instance_evel()方法可以使调用对象为self,当前类为#self(当前对象的eigenclass),并且传递一个代码块访问self;Module#class_evel()方法则可以使调用者成为当前类,并在当前类中执行传入的块(self此时是一个class的实例对象),从而修改当前类的方法和属性。Kernel#evel()方法则是调用一串代码字符串,并且执行这个字符串中的代码。
*evel()方法都可以传入一个Binding类,Binding类是一个只包含作用域的对象,通过传入Binding类,可以使*evel()方法在Binding类所携带的作用域中执行代码。
Binding类可以通过Kernel#binding()方法创建:
class MyClass
def my_method
@a = 1
binding
end
end
当执行 b = MyClass.new.my_method时, b就代表了一个作用域,通过 eval “puts @a” ,b 传入该绑定,获得@a。
使用evel方法可以很方便地实现一些功能,如计算器应用的编写等,但是在能力越大的同时,也有更大的危险。Kernel#evel方法执行时不会检查字符串代码的语法,并且会带来一些安全问题:代码注入。当evel方法面向很多其他用户的时候,就可以通过传入一些命令来获得你的私有信息,所以用evel方法时必须非常谨慎,Ruby中,也会有一些安全措施:
Ruby会把不安全的对象标记为被污染的(尤其是从外部传入的对象),通过tainted?()方法可以判断该字符串是否被污染,为了避免检查每个对象的污染情况,Ruby内置了安全级别来默认处理这些危险操作。通过$SAFE全局变量可以改变当前的安全级别,默认为0——不受任何约束;一共有5个安全级别,任何大于0的安全级别都不能执行污染的字符串。
为了安全地使用evel,可以为evel()方法创造一个沙盒,并且在沙盒中运行该字符串:
proc{
$SAFE = @safe_level
evel “cmd”,b
}.call
evel()方法的替代
为了避免evel()带来的安全问题,可以使用其他的方法来替代evel():通过动态派发:send()方法来调用方法;通过class_evel()来进入类,并为类添加方法或者实例变量,并且用define_method()方法来动态添加方法;使用Object#instance_variable_set()和Object#instance_variable_get()方法来设置或者访问实例变量。
钩子方法
通过改写Class#inherited()方法、Module#included()方法、Module#method_added()方法等等,可以在相应的事件发生时执行所需要的代码。如在包含一个模块的时候打印提示:
module M
def self.included(othermod)
puts “M was mixed into #(othermod)”
end
end
class C
include M
end
在C类中包含M模块时,会打印:M was mixed into C 提示字符串。
钩子方法默认实现时只是捕获一个事件,并不会执行其他的动作, 可以通过改写Module#include()方法来完成上述功能:
class C
def self.include(*modules)
puts “#(modules) is included in C”
super
end
include M
end
上述方法直接修改了include方法,由于include()方法除了捕获到事件之外,还有其他的事情要做,所以需要通过super来进行原始的工作,其中super的作用是在父类(当前类是C,父类是Module)中调用同名的函数,并且将本函数的所有参数传入到同名函数中。还有一个方法super()带括号,则表示调用父类中的同名函数,但是不传入任何参数。*表示传入多个参数打包为一个数组,并且在方法调用时解开数组使每个元素成为一个独立参数,通过*modules可以一次包含多个模块。
通过环绕别名来实现钩子方法:
class C
Class.class_eval {
alias :real_include :include
def include (mod)
real_include mod
puts "#{mod} was included!"
end
}
include M
end
类扩展混入
当一个类包含模块时,只会获得一组实例方法,而不会获得任何类方法,通过在eigenclass中包含一个模块来实现类扩展(或者使用extend()方法),如果一个模块期望被包含时一直可以作为类扩展,则可以通过在模块中添加钩子方法来实现:
module M
def self.included(base)
base.extend(ExtendMethods)
end
module ExtendMethods
def my_method()
#…
end
end
end
此时,如果在类中include M,则会调用钩子方法M.included(),将M中的子模块ExtendMethods(纯净室)作为类扩展,将Methods中的方法添加到包含类的eigenclass中。
上述将类扩展和钩子方法结合的技术叫做类扩展混入。而且,如果在M中又不需要被扩展的方法,则可以放到ExtendMethods外部定义一些额外的方法,这些方法不会被扩展为包含着的类方法。如果M中所有的方法都需要被扩展为类方法,则把所有的方法都定义在M本身即可。