`

Ruby的魔法 学习笔记二 meta-programming

    博客分类:
  • Ruby
阅读更多
meta-programming

1、Create a object on the fly.
Person = Class.new
p1 = Person.new
puts p1.class #Person

我们已经凭空创建了一个类Person,现在我们想添加点方法:
好的我们打开这个类:
class Person
  define_method :who? do
    puts "Me!"
  end
end 

p1.who? #Me!

我们发现p1可以使用who?这个方法了。

我们用另一中方法打开这个类,从一个数组读取一个两个字段
的名字,然后往Person里面属性和访问方法:
fields = ["name","age"]

Person.class_eval do
  attr_accessor *fields
  
  define_method :initialize do |*values|
      fields.each_with_index do |name,i|
        instance_variable_set("@"+name,values[i]);
      end
  end
end

p2 = Person.new("fuliang",25)
puts p2.name

我们发现class_eval这个block可以访问局部变量fields,这个block
在定义的时候与当前环境中的变量绑定了

同样我们可以看到define_method block也会和本地的变量绑定:
Counter = Class.new
shared_count = 0
Counter.send :define_method, :double_count do
	shared_count += 1
	@count ||= 0
	@count += 1
	[shared_count,@count]
end

first_counter = Counter.new
second_counter = Counter.new

p first_counter.double_count  #[1,1]
p first_counter.double_count  #[2,2]

p second_counter.double_count  #[3,1]
p second_counter.double_count  #[4,2]

现在我们想让一个实例mixin一个module,而不影响这个类的其他实例
o = Object.new.extend(Enumerable)
puts o.is_a?(Enumerable) #true
puts({}.is_a?(Enumerable)) #true
puts Object.new.is_a?(Enumerable) #false

给Object添加属性和方法将会影响所有的地方:
class Test
  o = Object.new
  Object.send :define_method, :next_for_all do
    @count_for_all = (@count_for_all || 0) + 1
  end

  puts o.next_for_all #1
  puts o.next_for_all #2 
  puts @count_for_all == nil #true is also the Test member field
  puts String.next_for_all #1
  puts String.next_for_all #2
  puts "".next_for_all #1
end

我们希望从文件中读取一些数据来动态的创建一个类以及它的属性和方法:
例如people.txt文件
引用

name,age,weight,height
"Smith, John", 35, 175, "5'10"
"Ford, Anne", 49, 142, "5'4"
"Taylor, Burt", 55, 173, "5'10"
"Zubrin, Candace", 23, 133, "5'6"

我们根据文件的名字创建一个类,然后读取文件的第一行,来添加一些属性,
然后定义一个read的类方法来把数据读出来创建Person对象保存到数组中
class DataRecord
  def self.make(file_name) #根据文件的数据动态创建类
    data = File.new(file_name)
    header = data.gets.chomp
    data.close
    class_name = File.basename(file_name,".txt").capitalize #people.txt -> People
    klass = Object.const_set(class_name,Class.new) #top-level的常量都是Object的一部分
    names = header.split(",")
    
    klass.class_eval do #使用class_eval打开类
      attr_accessor *names
      
      define_method(:initialize) do |*values| #使用define_method来定义initialize方法
        names.each_with_index do |name, i|
          instance_variable_set("@"+name, values[i]);
        end
      end
      
      define_method(:to_s) do #使用define_method来定义to_s方法。 
        str = "<#{self.class}:"
        names.each{|name| str << "#{name}=#{self.send(name)}"}
        str << ">"
      end
      alias_method :inspect, :to_s #定义to_s的一个别名inspect
    end
    
    def klass.read #定义一个读取数据创建对象的类方法
      array = []
      data = File.new(self.to_s.downcase+".txt")
      data.gets
      data.each do |line|
        line.chomp!
        values = eval("[#{line}]")
        array << self.new(*values) #创建对象,添加到数组中
      end
      data.close
      array
    end
    klass
  end

end

DataRecord.make("people.txt")
list = People.read
puts list[0] #<People:name=Smith, Johnage=35weight=175height=5'10>

2、使用meta-class/singleton-class
o,p = Object.new, Object.new

def o.say_hi
	puts "hello!"
end

o.say_hi #hello!
p.say_hi #NoMetodError

我们给当个对象添加了一个方法,而不影响其他的对象,仅仅对这个对象添加了一个
方法所以叫做singleton-class,又叫meta-class:他对这个对象创建了一个虚的class
来存放singleton-method.
我们可能认为singleton-class并不常见,事实上所有的class method都是Class实例
的singleton-methods
class Greeter
    def greet; 'hello!'; end
    
    def self.describe_greeting
      puts 'Mostly it''s just saying hello to people.'
    end
  end
  
  def Greeter.say_more
    puts 'saying hello more'
  end	
end

p Greeter.singleton_methods.sort #['describe_greeting', 'say_more']

使用<<定义singleton-class
jim = Greeter.new
class << jim
 def greet_enthu
   self.greet.upcase
 end
end
jim.greet_enthu #HELLO!



我们想给Object创建一个更容易使用的meta-class
class ::Object
  def metaclass
    class << self; self end
  end
end

hash = Hash.new

puts hash.metaclass.is_a?(Class) #true
puts hash.class != hash.metaclass #true
puts( {}.metaclass == {}.metaclass ) #false 对象之间并不共享metaclass
puts( {}.metaclass.superclass == Hash.metaclass )#true

使用class-method来重写子类:
当我们想创建DSL来定义类的信息是,最常遇到的困难是如何表现信息来让框架的
其他部分使用,我们看看Rails的一个例子
 class Product < ActiveRecord::Base
	set_table_name 'produce'
 end

set_table_name来告诉数据库的名字不是默认的products而是produce
它是怎么工作的呢?我们看看实现的代码:
module ActiveRecord
  class Base
      def self.set_table_name name
            define_attr_method :table_name, name
      end
      def self.define_attr_method(name, value)
         singleton_class.send :alias_method, "original_#{name}", name
         singleton_class.class_eval do 
		define_method(name) do 
                	value 
         	end
      	 end
     end
  end 
end

我们比较关注的是define_attr_method,这里的singleton_class是kernal中的方法
module Kernel
 def singleton_class
   class << self; self; end
 end
end

我们首先为这个字段创建了一个别名,然后定义一个访问方法,这样ActiveRecord 需要一个具体类
的table name,他只需要使用这个访问方法即可
3、使用method_missing 来做一些有趣的事情:
除了block,ruby最有威力的特点可能要算method_missing机制了,有些情况下使用它能够极大的简化代码,
但很容易被滥用,写一个对hash的扩展来说明它的威力:
class Hash
  def method_missing(m,*a)
     if m.to_s =~ /=$/
	self[$`] = a[0]
     elsif a.empty?
       self[m]
     else
       raise NoMethodError, "#{m}"
     end
  end
end

 x = {'abc' => 123}
 x.abc # => 123
 x.foo = :baz #调用method_missing,设置key-value
 x # => {'abc' => 123, 'foo' => :baz}

再看看Markaby的一段
body do
  h1.header 'Blog'
  div.content do
     "hellu"
  end
end
将会生成
  <body>
     <h1 class="header">Blog</h1>
     <div class="content">Hellu</div>
  </body>
  
如此优美的代码就是通过 method_missing 来实现的。
4、根据method的pattern来分发消息:
例如一个Unit Test类去调用任何一个以test_开头的测试方法:
 
methods.grep /^test_/ do |m|
    self.send m
end

5、使用alias,alias_method定义一个别名
在Java中我们常用super来调用父类的操作,然后在做一些自己的操作,来
覆写一些方法,在ruby中可以把一个方法定义别名,然后重写这个方法,
并且在这个方法中可以调用已定义的别名方法,这个别名方法保存了原始的
方法。例如
class String
   alias_method :original_reverse, :reverse

   def reverse
       puts "reversing, please wait..."
       original_reverse
   end
end

下面我们用这个技术来实现跟踪函数调用的方法:
class Object
  def trace(*mths)
    add_tracing(*mths)
    yield
    remove_tracing(*mths)
  end

  def singleton_class
    class << self; self; end
  end
  
  def add_tracing(*mths)
    mths.each do |m|
      singleton_class.send :alias_method, "traced_#{m}", m
      singleton_class.send :define_method, m do |*args|
        puts "before #{m}(#{args.inspect})"
        ret = self.send("traced_#{m}",*args)
        puts "after #{m}-#{ret.inspect}"
        ret
      end
    end
  end
  
  def remove_tracing(*mths)
    mths.each do |m|
      singleton_class.send :alias_method, m ,"traced_#{m}"
    end
  end
end


str = "abc"
str.trace(:reverse,:upcase){
  str.reverse
  str.upcase
}


6、使用NilClass来实现NullObject模式
Flowlers在重构那本书中提出了NullObject模式,在普通的面向对象语言中它通过子类化
来实现的。在ruby中可以使用NilClass,提供一个更简单的方法:
class << nil
  def name
    "default name"
  end
  def age
    "default age"
  end
end

x = nil
puts x.name
puts x.age

7、使用blocks创建Procs,并在周围使用他
def create_proc(&p)
  p
end
create_proc do
  puts "hello"
end

p1 = lambda{puts "hello"; return 1}
p2 = Proc.new {puts "hello"}
p1.call
p2.call

3
0
分享到:
评论
2 楼 orcl_zhang 2010-05-19  
fuliang 写道
在上面例子在ActiveRecord::Base中定义set_table_name,最大的威力是在子类调用这个方法的时候,会在父类ActiveRecord::Base动态定义name这个访问方法,这样父类在定义的时候可以通过这个访问方法来做事情。
也就是说父类定义了一些可以被子类调用来动态添加方法的方法,这些方法名都是动态的,可以根据子类调用方法传来的参数来得到方法名,这就是威力之所在。
Rails最让人感到爽的动态的查询:find_by_name,find_by_age,find_by_name_and_age都是通过Method Missing机制来实现的。

确实蛮爽,楼主写的真不错啊。
连续两篇都看完了。
引用
下面我们用这个技术来实现跟踪函数调用的方法:

这个rails实现了一个alias_method_chain方法,可以很方便的实现aop。不过再折腾无非还是多用几次alias_method
1 楼 fuliang 2009-02-24  
在上面例子在ActiveRecord::Base中定义set_table_name,最大的威力是在子类调用这个方法的时候,会在父类ActiveRecord::Base动态定义name这个访问方法,这样父类在定义的时候可以通过这个访问方法来做事情。
也就是说父类定义了一些可以被子类调用来动态添加方法的方法,这些方法名都是动态的,可以根据子类调用方法传来的参数来得到方法名,这就是威力之所在。
Rails最让人感到爽的动态的查询:find_by_name,find_by_age,find_by_name_and_age都是通过Method Missing机制来实现的。

相关推荐

    src-oepkgs/ruby-ruby2ruby

    src-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2...

    rh-ruby25-rubygems-devel-2.7.6-6.el7.noarch.rpm

    官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装

    ruby安装包-rubyinstaller-devkit-3.0.2-1-x64安装文件

    ruby安装包-rubyinstaller-devkit-3.0.2-1-x64安装文件 Ruby是一种面向对象、动态类型的脚本语言,由Yukihiro "Matz" Matsumoto于1995年创建。它以其简洁、优雅的语法和强大的编程能力而闻名,广泛应用于Web开发、...

    ruby-1.8.7-p358-i386.rar

    ruby-1.8.7-p358-doc-chm.7z 3.65 MB 1,399 Other Other ruby-1.8.7-p358-i386-mingw32.7z 5.12 MB 1,503 i386 Other rubyinstaller-1.8.7-p358.exe 11.69 MB 13,534 i386 .exe (Windows executable)

    ruby+selenium-webdriver测试--第一个例子源代码

    Ruby+Selenium-Webdriver是一个强大的自动化测试工具组合,用于模拟真实用户在浏览器中与网页进行交互。Ruby是一种动态、面向对象的编程语言,而Selenium WebDriver是一个开源的自动化测试框架,支持多种浏览器和...

    ruby-debug-base19-0.11.26.gem

    ruby-debug-base19-0.11.26.gem

    ruby安装包-rubyinstaller-devkit-3.0.2-1-x64.zip

    Ruby是一种面向对象、动态类型的脚本语言,由Yukihiro "Matz" Matsumoto于1995年创建。它以其简洁、优雅的语法和强大的编程能力而闻名,广泛应用于Web开发、脚本自动化、服务器管理等领域。RubyInstaller是Windows...

    ruby-oci8-2.1.5-x86-mingw32.gem

    ruby-oci8-2.1.5-x86-mingw32.gem,ruby连接oracle数据库gem包

    Ruby-Functional-Programming, 来自 Conferencia Rails 2011.zip

    Ruby-Functional-Programming, 来自 Conferencia Rails 2011 通过 Arnau Sanchez实现的ruby 函數式程式設計簡介理論部分Ruby的函數式程式設計不要更新變數不要重用變量用阻止作為高階函數物件導向與函數式程式設計萬...

    Programming-Ruby-1.9源代码

    《Programming Ruby 1.9》是一本经典的Ruby编程语言教程,其源代码包含了大量实例和示例,旨在帮助读者深入理解Ruby的语法、特性以及编程实践。这些源代码是学习和探索Ruby语言的重要资源,涵盖了从基础语法到高级...

    ruby-1.9.1-p0-i386-mswin32.rar

    在本压缩包“ruby-1.9.1-p0-i386-mswin32.rar”中,包含的是针对i386架构的Windows 32位系统的Ruby安装程序。这个版本(p0)意味着它是1.9.1主版本下的一个特定补丁级别,通常包括了一些错误修复和优化。 Ruby的...

    ruby笔记1ruby笔记1ruby笔记1

    标题中的"ruby笔记1ruby笔记1ruby笔记1"暗示了这是一份关于Ruby编程语言的学习笔记,可能包含了作者在学习过程中的重点、难点以及心得体验。描述中的内容重复了标题,进一步强调了这是关于Ruby的深入学习记录。标签...

    ruby-debug-ide19-0.4.12.gem

    ruby-debug-ide19-0.4.12.gem

    ruby-1.9.2-preview1-x64-mswin64-80.zip

    "ruby-1.9.2-preview1-x64-mswin64-80.zip" 是一个针对Windows平台的64位版本的Ruby 1.9.2预览版1的压缩包。这个版本在当时是一个重要的更新,因为它引入了许多改进和新特性。 1. **Ruby 1.9.2**: Ruby 1.9是Ruby...

    rh-ruby25-build-2.5-2.el7.x86_64.rpm

    官方离线安装包,亲测可用。使用rpm -ivh [rpm完整包名] 进行安装

    Programming Ruby(读书笔记)-3章

    《Programming Ruby》是一本关于Ruby编程语言的经典书籍,它的第三章深入探讨了Ruby的基本语法和核心概念。在这一章中,作者介绍了变量、常量、符号、数组、哈希等核心数据类型,以及控制流(条件语句和循环)和方法...

    ruby安装包-rubyinstaller-devkit-3.0.2-1-x64安装包

    ruby安装包-rubyinstaller-devkit-3.0.2-1-x64安装包 Ruby是一种面向对象、动态类型的脚本语言,由Yukihiro "Matz" Matsumoto于1995年创建。它以其简洁、优雅的语法和强大的编程能力而闻名,广泛应用于Web开发、脚本...

    ruby-oci8-1.0.3-x86-mswin32.gem

    ruby-oci8-1.0.3-x86-mswin32.gem

    ruby-1.9.1-p0-i386-mswin32.zip

    "ruby-1.9.1-p0-i386-mswin32.zip" 是一个针对Windows操作系统编译的Ruby编程环境的压缩包,发布于2009年6月20日,当时是Ruby 1.9.1版本的最新版。 Ruby 1.9.1是一个重要的版本更新,引入了许多改进和新特性,包括...

    meta-openembedded:OpenEmbedded图层的集合

    7. **meta-ruby**: 为 Ruby 开发者提供所需的环境。 8. **meta-openwrt**: 提供 OpenWRT 相关的软件包和配置。 **镜像(mirror)与 BitBake** 在描述中提到了 "mirror BitBake",这可能是指使用镜像站点来加速 ...

Global site tag (gtag.js) - Google Analytics