添加变量和方法

module_evalclass_eval 方法也可用于获取类变量的值(但请记住,你越这么做,代码就越依赖于类的实现细节,从而破坏封装性):

  1. Y.class_eval( "@@x" )

实际上,class_eval 可以计算任意复杂度的表达式。例如,你可以通过计算字符串将其用于向类中添加新方法…

  1. ob = X.new
  2. X.class_eval( 'def hi;puts("hello");end' )
  3. ob.hi #=> "hello"

回到前面从类外部添加和获取类变量的示例(使用 class_eval);事实证明,还有一些方法可以从类中实现。这些方法称为 class_variable_get(这需要一个表示变量名的符号参数,它返回变量的值)和 class_variable_set(这需要一个表示变量名的符号参数和一个要赋给变量的值作为第二个参数)。这是这些方法的一个示例:

classvar_getset.rb
  1. class X
  2. def self.addvar( aSymbol, aValue )
  3. class_variable_set( aSymbol, aValue )
  4. end
  5. def self.getvar( aSymbol )
  6. return class_variable_get( aSymbol )
  7. end
  8. end
  9. X.addvar( :@@newvar, 2000 )
  10. puts( X.getvar( :@@newvar ) ) #=> 2000

要获取类变量名称列表作为字符串数组,请使用 class_variables 方法:

  1. p( X.class_variables ) #=> ["@@abc", "@@newvar"]

你还可以使用 instance_variable_set 为类和对象在它们被创建后添加实例变量:

  1. ob = X.new
  2. ob.instance_variable_set("@aname", "Bert")

将此与添加方法的能力相结合,大胆的(或者可能是鲁莽的?)程序员可以完全改变“来自外部”类的内部结构。这里我以类 X 中名为 addMethod 的方法的形式实现了这个方法,它使用 send 方法创建一个新方法 m,该方法使用 define_method 和由 &block 定义的方法体:

dynamic.rb
  1. def addMethod( m, &block )
  2. self.class.send( :define_method, m , &block )
  3. end
send 方法调用第一个参数(符号)标识的方法,并将指定的其它参数传递给它。

现在,X 对象可以调用 addMethod 将新方法插入到 X 类中:

  1. ob.addMethod( :xyz ) { puts("My name is #{@aname}") }

虽然从类的特定实例(此处为 ob)调用此方法,但它会影响类本身,因此新定义的方法也可用于后续从 X 类创建的任何实例(此处为 ob2):

  1. ob2 = X.new
  2. ob2.instance_variable_set("@aname", "Mary")
  3. ob2.xyz

如果你不关心对象中数据的封装性,你还可以使用 instance_variable_get 方法获取实例变量的值:

  1. ob2.instance_variable_get( :@aname )

你可以类似地设置和获取常量:

  1. X::const_set( :NUM, 500 )
  2. puts( X::const_get( :NUM ) )

const_get 可以返回常量的值,所以你可以使用此方法获取类名的值,然后附加新方法以从该类创建新对象。这甚至可以通过提示用户输入类名和方法名来为你提供在运行时(runtime)创建对象的方法。通过运行此程序试试这个:

dynamic2.rb
  1. class X
  2. def y
  3. puts( "ymethod" )
  4. end
  5. end
  6. print( "Enter a class name: ") #<= Enter: X
  7. cname = gets().chomp
  8. ob = Object.const_get(cname).new
  9. p( ob )
  10. print( "Enter a method to be called: " ) #<= Enter: y
  11. mname = gets().chomp
  12. ob.method(mname).call