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