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

本文介绍了Ruby语言中的元编程技巧,包括动态创建类和方法、使用method_missing简化代码、基于方法名称分发消息、定义方法别名及使用NilClass实现NullObject模式等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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文件
[quote]
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"
[/quote]
我们根据文件的名字创建一个类,然后读取文件的第一行,来添加一些属性,
然后定义一个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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值