`
simohayha
  • 浏览: 1386554 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

ruby way之动态特性之二

    博客分类:
  • ruby
阅读更多
1 得到所定义的实体的列表

ruby的反射api能够使我们在运行时检测类和对象。因此我们下面将会介绍Module, Class, 和Object中的定义的一些方法。

Module模块有一个constants 的方法,它将会返回系统中所有的常量名,包括类名和模块名。nesting 方法则是返回当前调用点上的嵌套的模块的列表.

list = Math.constants    # ["E", "PI"]


Module#ancestors 返回指定的类或者模块的所有包含的类或者模块.

list = Array.ancestors
# [Array, Enumerable, Object, Kernel]


class_variables 方法返回给定的类和他的超类的所有类变量的一个表。included_modules 方法列出包含在这个类中的所有模块。

class Parent
  @@var1 = nil
end

class Child < Parent
  @@var2 = nil
end

list1 = Parent.class_variables   # ["@@var1"]
list2 = Array.included_modules   # [Enumerable, Kernel]


Class的方法instance_methods和public_instance_methods 是同义的。他们返回这个类的所有公有的方法。private_instance_methods和 protected_instance_methods 也就是返回私有和保护的实例方法。这几个方法都还带有一个参数,默认是true的。如果被设置为false,超类将不会被搜索。

n1 = Array.instance_methods.size                 # 121
n2 = Array.public_instance_methods.size          # 121
n3 = Array.private_instance_methods.size         # 71
n4 = Array.protected_instance_methods.size       # 0
n5 = Array.public_instance_methods(falsee).size  # 71


Object 类有很多操作实例的类似的方法。methods 方法将会返回这个对象的所有可以被调用的方法。调用 public_methods 方法,将会返回所有公有的方法。他也有一个参数来判断是否去父类搜索。private_methods, protected_methods,和singleton_methods 也都有类似的参数。

class SomeClass

  def initialize
    @a = 1
    @b = 2
  end

  def mymeth
    #...
  end

  protected :mymeth

end


x = SomeClass.new

def x.newmeth
  # ...
end

iv = x.instance_variables        # ["@b", "@a"]

p x.methods.size                   # 42

p x.public_methods.size            # 41
p x.public_methods(false).size     # 1

p x.private_methods.size           # 71
p x.private_methods(false).size    # 1

p x.protected_methods.size         # 1
p x.singleton_methods.size         # 1


2 测试调用栈

有时我们想要知道我们的调用者是谁,这个有时是非常有用的。看下面的例子:

def func1
  puts caller[0]
 end

def func2
  func1
end

func2              # 打印出fucn1在那里被调用



3 监控程序的执行

一个ruby程序能够内省,或者说是检测他自己的执行。很多程序都用到了这个功能,有兴趣的话可以看看这几个ruby的源码:debug.rb, profile.rb, 和 tracer.rb。

我们能够使用 set_trace_func方法,他接受一个block作为参数。无论在程序的执行中发生任何事情时,这个block都会被调用,我们看下面的例子:

def meth(n)
  sum = 0
  for i in 1..n
    sum += i
  end
  sum
end

set_trace_func(proc do |event, file, line,
                        id, binding, klass, *rest|
  printf "%8s %s:%d  %s/%s\n", event, file, line,
                               klass, id
end)

meth(2)


可以看到输出类似这样的:

引用
line D:/develop/rubyWorkspace/RubyWay/lib/DynamicFeaturesTest.rb:171  false/

    call D:/develop/rubyWorkspace/RubyWay/lib/DynamicFeaturesTest.rb:157  Object/meth

............................


还有一个方法是Kernel#trace_var,它是当全局变量被赋值时,才会被自动调用。

假设你想要在程序的外面得到程序的运行轨迹。最简单的方法就是使用tracer 库,假设有个prog.rb的文件:

def meth(n)
  (1..n).each {|i| puts i}
end

meth(3)


然后我们在命令行load TRacer :

引用
% ruby -r tracer prog.rb
#0:prog.rb:1::-:     def meth(n)
#0:prog.rb:1:Module:>:     def meth(n)
。。。。。。。。。。。。。。。


当源代码执行时每一个事件类型都包含"'-'",'>'表示一个调用,'<' 表示一个返回,'C'代表一个类,'E' 代表结束。

4 Traversing the Object Space

ruby的运行系统需要保存所有已知对象的踪迹(只是为了能够垃圾回收那些没有长时间使用的引用,也就是gc)。这个信息是通过 ObjectSpace.each_object 来得到的。

ObjectSpace.each_object do |obj|
  printf "%20s: %s\n", obj.class, obj.inspect
end


如果你指定一个类名或者模块名给each_object,它就只会返回这种类型的对象.

5 使用method_missing


起始和前面的const_missing差不多,也就是说当你在这个对象上调用一个不存在的方法时,它就会默认调用method_missing 方法:

class CommandWrapper

  def method_missing(method, *args)
    system(method.to_s, *args)
  end

end


cw = CommandWrapper.new
cw.date                   # Sat Apr 28 22:50:11 CDT 2001
cw.du '-s', '/tmp'        # 166749  /tmp


在Object中定义的method_missing方法是默认抛出一个异常的.

6 跟踪类或者对象的改变

我们现在想要写一个模块,它能够被任何类所包含,然后在这个类中的每调用一个方法,都会打印出相应的信息,比如我们所期待的是这样的:

class MyClass
  include Tracing

  def one
  end

  def two(x, y)
  end

end

m = MyClass.new
m.one                 # one called. Params =
m.two(1, 'cat')       # two called. Params = 1, cat


对于子类我们也是能够跟踪的:

class Fred < MyClass

  def meth(*a)
  end

end

Fred.new.meth(2,3,4,5)   # meth called. Params = 2, 3, 4, 5


下面来看它的实现:

module Tracing
       def Tracing.included(into)
         into.instance_methods(false).each { |m|
Tracing.hook_method(into, m) }
         def into.method_added(meth)
           unless @adding
             @adding = true
             Tracing.hook_method(self, meth)
             @adding = false
           end
         end
       end

       def Tracing.hook_method(klass, meth)
         klass.class_eval do
           alias_method "old_#{meth}", "#{meth}"
           define_method(meth) do |*args|
             puts "#{meth} called. Params = #{args.join(', ')}"
             self.send("old_#{meth}",*args)
           end
         end
       end
     end

     class MyClass
       include Tracing

       def first_meth
        end

       def second_meth(x, y)
       end
   end



m = MyClass.new
p m.first_meth                 # one called. Params =
p m.second_meth(1, 'cat')       # two called. Params = 1, cat


这个代码其实很简单.首先它有两个主方法,第一个是included,它是一个回调方法,当这个模块被插入到一个类中时,就会调用这个方法。在我们上面的例子中,他做了两件事,一件是为include这个模块的类的每一个方法调用hook_method方法,第二件事是,为这个类重新定义了method_added 方法。这就意味着,这个类如果加方法的话,就会调用这个方法,也就是说会被检测到。

而hook_method的实现也是很漂亮,使用了define_method来动态的定义方法,打印出信息后,再使用send来调用老的方法。

这里还要注意一个alias_method ,它和alias很类似,只不过它只能用在方法。并且他自己就是一个方法:

# Two other ways to write that line...

# Symbols with interpolation:
alias_method :"old_#{meth}", :"#{meth}"

# Strings converted via to_sym:
alias_method "old_#{meth}".to_sym, meth.to_sym


检测一个新的类方法被加到一个类或者模块,我们能够定义一个类方法singleton_method_added :

class MyClass

  def MyClass.singleton_method_added(sym)
    puts "Added method #{sym.to_s} to class MyClass."
  end

  def MyClass.meth1
    puts "I'm meth1."
  end

end

def MyClass.meth2
  puts "And I'm meth2."
end


输出将会是这样子:

引用
Added method singleton_method_added to class MyClass.
Added method meth1 to class MyClass.
Added method meth2 to class MyClass.


inherited 方法使用也很类似,当一个类被子类化,就会调用这个方法:

class MyClass

  def MyClass.inherited(subclass)
    puts "#{subclass} inherits from MyClass."
  end

  # ...

end


class OtherClass < MyClass

  # ...

end

# Output: OtherClass inherits from MyClass.


我们也能够跟踪加一个模块的实例方法到一个对象。extend_object 方法就是做这个的:

module MyMod

  def MyMod.extend_object(obj)
    puts "Extending object id #{obj.object_id}, class #{obj.class}"
    super
  end

  # ...

end


x = [1, 2, 3]
x.extend(MyMod)

# Output:
# Extending object id 36491192, type Array


这里要注意super是必须的,和append_features 重的原因一样。

7 为对象定义一个Finalizers

ruby类有构造器,可是没有析构器。原因很简单,就是因为ruby使用了mark-and-sweep garbage collection 来删除没有引用的对象。

虽然在ruby中不能真正的做到调用析构器来删除对象。这里有个方法define_finalizer,它是当一个对象被gc时,就会调用它:

a = "hello"
puts "The string 'hello' has an object id #{a.object_id}"
ObjectSpace.define_finalizer(a) { |id| puts "Destroying #{id}" }
puts "Nothing to tidy"
GC.start
a = nil
puts "The original string is now a candidate for collection"
GC.start


输出的结果类似这样的:

The string 'hello' has an object id 21089680

Nothing to tidy

The original string is now a candidate for collection

Destroying 21089680


当finalizer 被调用的同时对象就被销毁了。如果此时你调用ObjectSpace._id2ref,然后参数为刚才那个对象的id,则会报一个RangeError:

ObjectSpace._id2ref 21089630 #in `_id2ref': 0x141cd5e is recycled object (RangeError)


由于ruby使用的是mark-and-sweep GC 的策略,那么他就不能保证当程序结束之前,对象什么时候被GC。

可是所有的一切都是不确定的。在ruby中,经常使用block来压缩一个源(也就是一段代码)的使用,在block的结尾,这个源将会被删除(也就是说。然后其他的对象没有任何改变(其实这个也就是说讲代码封装在block里面比较好,因为当block退出时,它的那些局部变量,或者说,传进来的哪些参数都会被GC掉,因此在ruby1.9里面,block里面的局部变量和外面的变量已经不是同一个变量了(假设名字相同)。)。看下面的代码:

File.open("myfile.txt") do |file|
  line1 = file.read
  # ...
end


在这里当block退出时,file 被删除,这一切都是在open方法里控制。如果你想要实现一个open方法的子集,你可以这么做:

def File.open(name, mode = "r")
  f = os_file_open(name, mode)
  if block_given?
    begin
      yield f
    ensure
      f.close
    end
    return nil
  else
    return f
  end
end


这边使用begin ensure是因为要做到就算异常被抛出,文件也要被关闭.
7
0
分享到:
评论
2 楼 simohayha 2008-01-25  
Module#ancestors 返回指定的类或者模块的所有存取方法。
--------------------
这个说错了吧,是返回所有包含的模块

哈哈,恩,错了。。。。
1 楼 dennis_zane 2008-01-25  
Module#ancestors 返回指定的类或者模块的所有存取方法。
--------------------
这个说错了吧,是返回所有包含的模块

相关推荐

Global site tag (gtag.js) - Google Analytics