元编程的魅力——反射机制

DeathKing posted @ 2011年1月27日 23:43 in Ruby with tags 元编程 metaprogramming Metaclass 反射机制 反射 reflection , 17516 阅读

  从元编程Metaprogramming)的角度来看,一门语言同时也是自身的元语言的能力称之为反射Reflection)。当然,我们更习惯把反射看做是一个程序的自我访问、检测以及修改的能力。合理、高效的利用反射机制,我们可以加快项目的开发,快速找出漏洞并将其解决。反射,无论在什么时候,都是我们应该利用的工具。

  Ruby是一个高效利用元编程的语言,反射也充斥使用Ruby的编程中。因此,我们将通过对Ruby中反射机制的讨论,来感受元编程的魅力,感受反射机制。

  还记得曾经将威廉·莎士比亚W. William Shakespeare)的著名作品《哈姆雷特》(Hamlet)中的名句(To be, or not to be- that is the question)重新创作为Ruby语句:

_2b || !(_2b)
that.is_a? Question

  代码并不优美,同时也需要预先处理才能保证代码不会出错,但是,这段短小的代码,已经引出了反射机制中的一个重要方法is_a?方法。

  is_a?Object类提供并接受一个参数,参数为类名的标识符(一个常量标识符),该方法用于确认对象是否为指定类的实例。区别与instance_of?is_a?的判定条件比较宽松,ri提供的例子就足够详细了,让我们来看看吧。

module M; end
class A; include M; end
class B < A; end
class C < B; end

b = B.new
b.instance_of? A  #=> false
b.instance_of? B  #=> true
b.instance_of? C  #=> false
b.instance_of? M  #=> false

b.kind_of? A      #=> true
b.kind_of? B      #=> true
b.kind_of? C      #=> false
b.kind_of? M      #=> true

  为什么没有is_a?方法?因为kind_of?方法和is_a?方法是同义词,效果及用法一样。我们从上述例子中不难看出,如果给定的类,是直接生成对象的类,那么,instance_of?方法才返回true。而如果是直接生成对象的类的父类或者引用的模块、父类引用的模块发,instance_of?就会无情的返回false。不过不要紧,kind_of?方法(is_a?方法也如此)会返回true的。这个机制联系到后面涉及到的反射或许会更易理解。

  除了这样确定一个实例所属的类,也可以通过class方法取得,当然,这里的class并不是定义类的关键字,是由Object类提供的实例方法。用于对象直接属于的类。记得上面的那个例子么?不要关闭它,在irb中继续尝试。

irb(main):015:0> b.class
=> B

  对,instance_of?方法更像是这样比较(但其底层实现或许并不是这样):

class Obeject
  def instance_of?(a_class)
    return a_class == self.class
  end
end

  如果想要了解kind_of?is_a?的运作,我们就要了解一下superclass方法。与前面提到的反射用到的方法不同,superclass方法是由Class类提供,也就是,只有类才能使用这个实例方法。而superclass方法返回一个类的父类。

irb(main):016:0> b.class.superclass
=> A

  没错,记得我们的定义么?类B是由类A继承过来的,那么类B的父类就是类A。如果你很好奇,可以不断的使用superclass方法层层深入,虽然这样会让你陷入另一个问题——Ruby的顶层。

irb(main):22:0> b.class
=> B
irb(main):023:0> b.class.superclass
=> A
irb(main):024:0> b.class.superclass.superclass
=> Object
irb(main):025:0> b.class.superclass.superclass.superclass
=> BasicObject
irb(main):026:0> b.class.superclass.superclass.superclass.superclass
=> nil

  对,可以认为kind_of?方法枚举了直接负责对象的类,以及该类的父类。只要枚举出的类中有一个与指定的类相符,该方法就返回true。当然,还有引用的模块。

  Module类的实例方法included_modules(一会儿过去式,一会儿三单现的命名让我很恼火)可以返回一个模块或类包含的模块(Class继承自Module,自然继承了此方法)。

irb(main):033:0> B.included_modules
=> [M, Kernel]

  不用担心Kernel模块,诚如教科书所写,他包含在每一个类或模块里,这样才使得Ruby中有了“函数”。而M模块,正是因为B的父类A,包含了模块M所致。

  好了,这下应该很清楚了。instance_of?方法只会着眼于眼前局面,而kind_of?顾全大局。不过一大串的superclass很让人不习惯啊。于是,ancestors方法出现了,老祖宗的力量果然不一般。

irb(main):034:0> B.ancestors
=> [B, A, M, Object, Kernel, BasicObject]

  老祖宗方法(别笑,这样称呼ancestors方法会很有趣)会罗列出该类的族谱,练包含的模块也不会落下。这样,我们就有了实现kind_of?另一种点子了。

class Obejct
  def kind_of?(a_class)
    return self.ancestors.include? a_class
  end
end

b.kind_of? B   #=> true

  反射机制就这么点么?当然不,我们还有更绚丽的技巧。

  Kernel模块提供的local_variablesglobal_variablesObject类提供的instance_variables以及Module模块的constants方法,都大有用途。

  local_variables方法以数组的方式(元素是符号,1.9.X特性)返回建立的局部变量,不过受到作用域的限制。

irb(main):042:0> def i_am_a_temp_method
irb(main):043:1>   temp_var = 1
irb(main):044:1>   puts local_variables
irb(main):045:1> end
=> nil

irb(main):046:0> local_variables
=> [:b, :_]
irb(main):047:0> i_am_a_temp_method
temp_var
=> nil

  global_variables方法返回全局变量(Global Variable)。

irb(main):048:0> global_variables
=>[:$;, :$-F, :$@, :$!, :$SAFE, :$~, :$&, :$`, :$', :$+, :$=, :$KCODE, :$-K, :$,, :$/, :$-0, :$\, :$_, :$stdin, :$stdout, :$stderr, :$>, :$<, :$., :$FILENAME,:$-i, :$*, :$?, :$$, :$:, :$-I, :$LOAD_PATH, :$", :$LOADED_FEATURES, :$VERBOSE,:$-v, :$-w, :$-W, :$DEBUG, :$-d, :$0, :$PROGRAM_NAME, :$-p, :$-l, :$-a, :$binding, :$1, :$2, :$3, :$4, :$5, :$6, :$7, :$8, :$9]

  为了做示范,我们得初始化一个实例变量(Instance Variable)。

irb(main):052:0>class A
irb(main):053:1>  @yet_another_instance_variables = 0
irb(main):054:1>end
=> nil

irb(main):055:0>A. instance_variables
=> [:@yet_another_instance_variables]

  反射出类中的常量,以Math模块快速示范:

irb(main):056:0> Math.constants
=> [:PI, :E]

  反射机制还有一个功能,可以列出对象的一些行为(方法),做了一些省略:

irb(main):001:0> 1.methods
=>[:to_s, :-@, :+, :-, :*, :/, :div, :%, :modulo, :divmod, :fdiv, :**, :abs, :magnitude, :==, :<=>, :>, :>=, :<, :<=, :~, :&, :|, :^, :[], :<<, :>>, :to_f, :size, :zero?, :odd?, :even?, :succ, :integer?, :upto, :downto, :times, :next, :pred, :chr, :ord, ...

  Object#methods方法用数组的方式返回对象所拥有的方法,包括从祖先那里继承来的方法。而Class#instance_methods则是返回由类实例化的对象能拥有的方法,请与methods方法区分。

irb(main):005:0> Fixnum.instance_methods
=>[:to_s, :-@, :+, :-, :*, :/, :div, :%, :modulo, :divmod, :fdiv, :**, :abs, :magnitude, :==, :<=>, :>, :>=, :<, :<=, :~, :&, :|, :^, :[], :<<, :>>, :to_f, :size, :zero?, :odd?, :even?, :succ, :integer?, :upto, :downto, :times, :next, :pred, :chr, :ord, :to_i, :to_int, :floor, :ceil, :truncate, :round, :numerator, :denominator, :gcd, :lcm, :gcdlcm, ...

irb(main):006:0> Fixnum.instance_methods == Fixnum.methods
=> false

  而Object#singleton_methods方法同样以数组的方式返回对象的单例方法(Singleton Method)。

irb(main):007:0> Math.singleton_methods
=> [:atan2, :cos, :sin, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh,:asinh, :atanh, :exp, :log, :log2, :log10, :sqrt, :cbrt, :frexp, :ldexp, :hypot, :erf, :erfc, :gamma, :lgamma]

  这些方法,可以在发生NoMethodError后帮助我们调试。

  使用object_id获取对象的一个全局id,可以用于确定对象是不是同一个对象。

irb(main):010:0> 1.object_id == 1.object_id
=> true

irb(main):011:0> "obj".object_id == "obj".object_id
=> false

irb(main):012:0> :sym.object_id == :sym.object_id
=> true

  Fixnum类的实例都是立即数因此只有一个副本,Symbol类的对象也一样,但是其他类的可能就不一样咯~

 

Su 说:
2011年2月08日 16:44

Kernel 只是被 mixed-in 到了 Object 类,但由于任何对象都是 Object 的直接或派生类实例(1.9 的 BasicObject 实例除外),所以任何对象都能响应被 mixed-in 后原 Kernel 模块的方法。

11 说:
2012年8月09日 09:07

写这么多,我还不知道,这些代码与反射机制有什么关系。

DeathKing 说:
2012年8月13日 09:07

事实上,这就就是反射机制啊。所谓“反射”,就是一个内省。

dd 说:
2012年8月21日 08:53

反射机制, 我宁愿理解为烦死机制

lanrion 说:
2013年7月29日 15:17

只是使用Object的方法,或者可以理解这篇文章为Object的介绍。

ddlo2er 说:
2015年6月24日 09:05

这就是元编程的魅力?你的理解程度还真是令人吃惊,甚至还写了一篇文章,介绍了一下并不出彩的地方,告诉我们这就是“元编程”。拜托不要这样嘛,多学习学习再说话。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter