当你个思路方法时你也就是发送了条消息给个对象在ruby中我们能够在运行时决定那个思路方法被send 思路方法就是做这个他接受个symbol为参数.
举个简单例子假设我们要写个排序我们想要使用区别域作为比较key虽然我们这时可以用block可是如果使用send话我们能有个更优美写法:
Java代码
Person
attr_reader :name, :age, :height
def initialize(name, age, height)
@name, @age, @height = name, age, height
end
def inspect
"#@name #@age #@height"
end
end
Array
def sort_by(sym) # Our own version of sort_by
self.sort {|x,y| x.send(sym) <=> y.send(sym) }
end
end
people =
people << Person.("Hansel", 35, 69)
people << Person.("Gretel", 32, 64)
people << Person.("Ted", 36, 68)
people << Person.("Alice", 33, 63)
p1 = people.sort_by(:name)
p2 = people.sort_by(:age)
p3 = people.sort_by(:height)
p p1 # [Alice 33 63, Gretel 32 64, Hansel 35 69, Ted 36 68]
p p2 # [Gretel 32 64, Alice 33 63, Hansel 35 69, Ted 36 68]
p p3 # [Alice 33 63, Gretel 32 64, Ted 36 68, Hansel 35 69]
__send__ 其实也就是send思路方法别名了不过这边建议用__send__,这是send有可能作为个用户自己定义思路方法
在1.9中send思路方法不能private思路方法了不过我们能够使用__send!来:
Java代码
Foo
private
def foo
"aa"
end
end
p Foo..__send!(:foo) # => nil
p Foo..send(:foo) #private method `foo' called for #<Foo:0xa89530> (NoMethodError)
2 特殊化个单独对象
在很多oo语言里个类所有对象共享相同行为类作为个模板当构造器时制造个拥有相同接口对象
ruby中我们可以改变个对象运行时状态你可以给对象个私有匿名子类:所有原始思路方法都是可用由于联系到这个对象上行为是私有因此它只发生次件只发生次事叫做“singleton”比如singleton 思路方法和singleton类.
看下面代码:
Java代码
a = "hello"
b = "goodbye"
def b.up # create single method
gsub(/(.)(.)/) { $1.up + $2 }
end
puts a.up # HELLO
puts b.up # GoOdBye
加个singleton 思路方法到个对象也就是创建了个singleton 类然后这个类父类是这个对象类如果你加多个思路方法到个对象这是你可以直接实现个singleton 类:
Java代码
b = "goodbye"
<< b
def up # create single method
gsub(/(.)(.)/) { $1.up + $2 }
end
def up!
gsub!(/(.)(.)/) { $1.up + $2 }
end
end
puts b.up # GoOdBye
puts b # goodbye
b.up!
puts b # GoOdBye
这里要注意是些更“primitive”对象(比如Fixnum)不能加singleton 思路方法这是这些对象不是存引用在内存中但是在ruby将来版本可能会实现这个
在些库源码中我们能看到这种代码:
Java代码
SomeClass
# Stuff...
<< self
# more stuff...
end
# ... and so on.
end
在个类体内self 就指这个类自己在这个类中例子思路方法其实也就是外面类类思路方法:
Java代码
TheClass
<< self
def hello
puts "hi"
end
end
end
# invoke a method
TheClass.hello # hi
使用这个技术另个原因是可以创建个类级别帮助思路方法然后我们就能在这个类其他地方使用它了.
Java代码
MyClass
<< self
def accessor_(*names)
names.each do |name|
_eval <<-EOF
def #{name}
@#{name}.to_s
end
EOF
end
end
end
def initialize
@a = [ 1, 2, 3 ]
@b = Time.now
end
accessor_ :a, :b
end
o = MyClass.
puts o.a # 123
puts o.b # Mon Apr 30 23:12:15 CDT 2001
extend 思路方法能够mix个模块到个对象:
Java代码
module Quantier
def any?
self.each { |x| true yield x }
false
end
def all?
self.each { |x| false not yield x }
true
end
end
list = [1, 2, 3, 4, 5]
list.extend(Quantier)
flag1 = list.any? {|x| x > 5 } # false
flag2 = list.any? {|x| x >= 5 } # true
flag3 = list.all? {|x| x <= 10 } # true
flag4 = list.all? {|x| x % 2 0 } # false
3 创建个带参数类
假设我们想要创建个多样类也就是说可以通过控制类变量来控制它多种状态:
Java代码
Terran
@@home_planet = "Earth"
def Terran.home_planet
@@home_planet
end
def Terran.home_planet=(x)
@@home_planet = x
end
#...
end
这样是可以这时如果我想要定义些和Terran类似类你可能会马上想到是可以给这些类抽象出来个超类就行了:
(注意这里是思路方法)
Java代码
IntelligentLe # Wrong way to do this!
@@home_planet = nil
def IntelligentLe.home_planet
@@home_planet
end
def IntelligentLe.home_planet=(x)
@@home_planet = x
end
#...
end
Terran < IntelligentLe
@@home_planet = "Earth"
#...
end
Martian < IntelligentLe
@@home_planet = "Mars"
#...
end
当你Terran.home_planet时在1.9中会打印出nil在1.8中会打印出Mars.
为什么会这样?答案是 variables 不是真正 variables 他们不属于类而是属于整个继承体系 variables不能从父类所被复制但是能够从父类所被共享
我们可以剔除掉类变量在基类中定义可是这时我们定义类思路方法就不能工作了
这里有个稍好些思路方法使用了_eval 思路方法:
Java代码
IntelligentLe
def IntelligentLe.home_planet
_eval("@@home_planet")
end
def IntelligentLe.home_planet=(x)
_eval("@@home_planet = #{x}")
end
#...
end
Terran < IntelligentLe
@@home_planet = "Earth"
#...
end
Martian < IntelligentLe
@@home_planet = "Mars"
#...
end
puts Terran.home_planet # Earth
puts Martian.home_planet # Mars
这个可以打印出我们想要结果任何IntelligentLe里定义例子变量或者例子思路方法都会被Terran 和 Martian所继承
下面思路方法可能是最好思路方法我们没有使用类变量而是使用类例子变量:
Java代码
IntelligentLe
<< self
attr_accessor :home_planet
end
#...
end
Terran < IntelligentLe
self.home_planet = "Earth"
#...
end
Martian < IntelligentLe
self.home_planet = "Mars"
#...
end
puts Terran.home_planet # Earth
puts Martian.home_planet # Mars
这里我们打开了个singleton 定义了个存取思路方法home_planet两个子类他们自己accessors 来设置变量.我们其实还可以给home_planet=思路方法设置为 private
这里其实还可以这样做:
Java代码 module IntelligentLe
attr_accessor :home_planet
end
Terran
<< self
IntelligentLe
end
self.home_planet = "Earth"
#...
end
Martian
<< self
IntelligentLe
end
self.home_planet = "Mars"
#...
end
puts Terran.home_planet # Earth
puts Martian.home_planet # Mars
4 使用Continuations 来实现个生成器
ruby个更抽象特性就是continuation这是种控制非局部跳转和返回思路方法个continuation 对象存储着个返回地址和个上下文.他看起来很像c中jmp/longjmp 可是他存储着更多上下文.
Kernel 思路方法callcc接受个block返回个Continuation 对象这个被返回对象作为个参数被传递进这个block.Continuation唯思路方法就是call它将会引起个非局部返回执行 callcblock直到它尾部
其实Continuation很像游戏中保存游戏特性最好理解Continuation思路方法就是去看电影Run, Lola, Run (哈哈就是罗拉快跑).
Continuation最好例子就是生成器现在生成器已经是ruby部分了
请看下面使用生成器Fibonacci numbers 例子:
Java代码
Generator
def initialize
do_generation
end
def next
callcc do |here|
@_context = here;
@generator_context.call
end
end
private
def do_generation
callcc do |context|
@generator_context = context;
end
generating_loop
end
def generate(value)
callcc do |context|
@generator_context = context;
@_context.call(value)
end
end
end
# Sub this and a generating_loop
FibGenerator < Generator
def generating_loop
generate(1)
a, b = 1, 1
loop do
generate(b)
a, b = b, a+b
end
end
end
# Now instantiate the ...
fib = FibGenerator.
puts fib.next # 1
puts fib.next # 1
puts fib.next # 2
puts fib.next # 3
puts fib.next # 5
puts fib.next # 8
puts fib.next # 13
这里要注意是continuations性能不好它保存了太多状态和上下文...
延伸阅读
最新评论