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

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

你可能感兴趣的:(AOP,框架,Ruby,ActiveRecord,Rails)