Ruby的魔法 学习笔记之一

  Ruby的许多动态特性,让Ruby具有很多魔法,这个魔法足以让你来定制你自己的语言DSL, 
Rails就是Ruby在Web的DSL. 

一、向对象显示的发送消息 
我们可以向对象直接发送消息: 
Ruby代码 
  1. class HelloWorld  
  2.     def say(name)  
  3.         print "Hello, ", name  
  4.     end  
  5. end  
  6. hw = HelloWorld.new  
  7. hw.send(:say,"world")  

我们通常使用hw.say("world"),但send可以对private的方法起作用。 
不光如此send可以使程序更加动态,下面我们看看一个例子: 
我们定义了一个类Person,我们希望一个包含Person对象的数组能够按 
照Person的任意成员数据来排序: 
Ruby代码 
  1. class Person  
  2.     attr_reader :name,:age,:height  
  3.       
  4.     def initialize(name,age,height)  
  5.       @name,@age,@height = name,age,height  
  6.     end  
  7.       
  8.     def inspect  
  9.      "#@name #@age #@height"  
  10.     end  
  11. end  

在ruby中任何一个类都可以随时打开的,这样可以写出像2.days_ago这样优美 
的code,我们打开Array,并定义一个sort_by方法: 
Ruby代码 
  1. class Array  
  2.     def sort_by(sysm)  
  3.         self.sort{|x,y| x.send(sym) <=> y.send(sym)}  
  4.     end  
  5. end  

我们看看运行结果: 
Ruby代码 
  1. people = []  
  2. people << Person.new("Hansel",35,69)  
  3. people << Person.new("Gretel",32,64)  
  4. people << Person.new("Ted",36,68)  
  5. people << Person.new("Alice", 33, 63)  
  6.   
  7. p1 = people.sort_by(:name)  
  8. p2 = people.sort_by(:age)  
  9. p3 = people.sort_by(:height)  
  10.   
  11. p p1   # [Alice 33 63, Gretel 32 64, Hansel 35 69, Ted 36 68]  
  12. p p2   # [Gretel 32 64, Alice 33 63, Hansel 35 69, Ted 36 68]  
  13. p p3   # [Alice 33 63, Gretel 32 64, Ted 36 68, Hansel 35 69]  

这个结果是如何得到的呢? 
其实除了send外还有一个地方应该注意attr_reader,attr_reader相当于定义了name, 
age,heigh三个方法,而Array里的sort方法只需要提供一个比较方法: 
x.send(sym) <=> y.send(sym) 通过send得到person的属性值,然后在使用<=>比较 
二、定制一个object 
<< object 
ruby不仅可以打开一个类,而且可以打开一个对象,给这个对象添加或定制功能,而不影响 
其他对象: 
Ruby代码 
  1. a = "hello"  
  2. b = "goodbye"  
  3.   
  4. def b.upcase  
  5.  gsub(/(.)(.)/)($1.upcase + $2)  
  6. end  
  7.   
  8. puts a.upcase #HELLO  
  9. puts b.upcase #GoOdBye  

我们发现b.upcase方法被定制成我们自己的了 
如果想给一个对象添加或定制多个功能,我们不想多个def b.method1 def b.method2这么做 
我们可以有更模块化的方式: 
Ruby代码 
  1. b = "goodbye"  
  2.   
  3. class << b  
  4.   
  5.   def upcase      # create single method  
  6.     gsub(/(.)(.)/) { $1.upcase + $2 }  
  7.   end  
  8.   
  9.   def upcase!  
  10.     gsub!(/(.)(.)/) { $1.upcase + $2 }  
  11.   end  
  12.   
  13. end  
  14.   
  15. puts b.upcase  # GoOdBye  
  16. puts b         # goodbye  
  17. b.upcase!  
  18. puts b         # GoOdBye  

这个class被叫做singleton class,因为这个class是针对b这个对象的。 
和设计模式singleton object类似,只会发生一次的东东我们叫singleton. 

<< self 给你定义的class添加行为 
Ruby代码 
  1. class TheClass  
  2.     class << self  
  3.         def hello  
  4.             puts "hello!"  
  5.         end  
  6.     end  
  7. end  
  8. TheClass.hello #hello!  

<的helper方法,然后在这个class的其他的定义中使用。下面一个列子定义了访问 
函数,我们希望访问的时候把成员数据都转化成string,我们可以通过这个技术来 
定义一个Class-Level的方法accessor_string: 
Ruby代码 
  1. class MyClass  
  2.   class << self  
  3.     def accessor_string(*names)  
  4.       names.each do |name|  
  5.         class_eval <<-EOF  
  6.           def #{name}  
  7.             @#{name}.to_s  
  8.           end  
  9.         EOF  
  10.       end  
  11.     end  
  12.   end  
  13.   
  14.   def initialize  
  15.     @a = [ 1, 2, 3 ]  
  16.     @b = Time.now  
  17.   end  
  18.     
  19.   accessor_string :a:b  
  20. end  
  21.   
  22. o = MyClass.new  
  23. puts o.a           # 123  
  24. puts o.b           # Fri Nov 21 09:50:51 +0800 2008  

通过extend module给你的对象添加行为,module里面的方法变成了对象里面的 
实例方法: 
Ruby代码 
  1. module Quantifier  
  2.   
  3.   def any?  
  4.     self.each { |x| return true if yield x }  
  5.     false  
  6.   end  
  7.   
  8.   def all?  
  9.     self.each { |x| return false if not yield x }  
  10.     true  
  11.   end  
  12.   
  13. end  
  14.   
  15. list = [1, 2, 3, 4, 5]  
  16.   
  17. list.extend(Quantifier)  
  18.   
  19. flag1 = list.any? {|x| x > 5 }        # false  
  20. flag2 = list.any? {|x| x >= 5 }       # true  
  21. flag3 = list.all? {|x| x <= 10 }      # true  
  22. flag4 = list.all? {|x| x % 2 == 0 }   # false  

三、创建一个可参数化的类: 
如果我们要创建很多类,这些类只有类成员的初始值不同,我们很容易想起: 
Ruby代码 
  1. class IntelligentLife   # Wrong way to do this!  
  2.   
  3.   @@home_planet = nil  
  4.   
  5.   def IntelligentLife.home_planet  
  6.     @@home_planet  
  7.   end  
  8.   
  9.   def IntelligentLife.home_planet=(x)  
  10.     @@home_planet = x  
  11.   end  
  12.   
  13.   #...  
  14. end  
  15.   
  16. class Terran < IntelligentLife  
  17.   @@home_planet = "Earth"  
  18.   #...  
  19. end  
  20.   
  21. class Martian < IntelligentLife  
  22.   @@home_planet = "Mars"  
  23.   #...  
  24. end  

这种方式是错误的,实际上Ruby中的类成员不仅在这个类中被所有对象共享, 
实际上会被整个继承体系共享,所以我们调用Terran.home_planet,会输出 
“Mars”,而我们期望的是Earth 
一个可行的方法: 
我们可以通过class_eval在运行时延迟求值来达到目标: 
Ruby代码 
  1. class IntelligentLife  
  2.   
  3.   def IntelligentLife.home_planet  
  4.     class_eval("@@home_planet")  
  5.   end  
  6.   
  7.   def IntelligentLife.home_planet=(x)  
  8.     class_eval("@@home_planet = #{x}")  
  9.   end  
  10.   
  11.   #...  
  12. end  
  13.   
  14. class Terran < IntelligentLife  
  15.   @@home_planet = "Earth"  
  16.   #...  
  17. end  
  18.   
  19. class Martian < IntelligentLife  
  20.   @@home_planet = "Mars"  
  21.   #...  
  22. end  
  23.   
  24.   
  25. puts Terran.home_planet            # Earth  
  26. puts Martian.home_planet           # Mars  

最好的方法: 
我们不使用类变量,而是使用类实例变量: 
Ruby代码 
  1. class IntelligentLife  
  2.   class << self  
  3.     attr_accessor :home_planet  
  4.   end  
  5.   
  6.   #...  
  7. end  
  8.   
  9. class Terran < IntelligentLife  
  10.   self.home_planet = "Earth"  
  11.   #...  
  12. end  
  13.   
  14. class Martian < IntelligentLife  
  15.   self.home_planet = "Mars"  
  16.   #...  
  17. end  
  18.   
  19.   
  20. puts Terran.home_planet            # Earth  
  21. puts Martian.home_planet           # Mars  

四、Ruby中的Continuations: 
Continuations恐怕是Ruby中最难理解的概念了,它可以处理非局部的跳转, 
它保存了返回地址和执行的环境,和c中的setjmp和longjump类似,但它保存 
了更多的信息: 
axgle举的曹操的例子很形象,我们拿过来看看: 
来自[http://www.javaeye.com/topic/44271] 
曹操(caocao)被誉为“古代轻功最好的人 ”,是因为“说曹操,曹操到”这句名言。 
在ruby中,曹操的这种轻功被称为callcc. 
Ruby代码 
  1. callcc{|caocao|    
  2.  for say in ["曹操","诸葛亮","周瑜"]       
  3.   caocao.call if say=="曹操"    
  4.   puts say #不会输出,因为曹操已经飞出去了    
  5.  end    
  6. }#“曹操”飞到这里来了(跳出了callcc的block,紧跟在这个block后面,继续执行下面的ruby代码)    
  7. puts "到"    

callcc里的caocao是个"延续"(Continuation)对象.这个对象只有名叫“call"的这样一个方法。 
当执行了caocao.call后,caocao就会飞到callcc的块(block)后面,让ruby继续执行其下面的代码。 

我上面给出的是一个从块里头”飞“到块外面的例子;下面是Programming Ruby给出的从代码后面”飞“到代码前面的例子: 
Ruby代码 
  1. arr = [ "Freddie""Herbie""Ron""Max""Ringo" ]    
  2. callcc{|$cc|}#下面的$cc.call如果被执行,就会飞回到这里(callcc的块之后)。    
  3. puts(message = arr.shift)    
  4. $cc.call unless message =~ /Max/   

例子大多来自<>

你可能感兴趣的:(ruby,class,accessor,string,module,设计模式)