ruby way之高级OOP特性之一

1 发送一条消息给一个对象

当你调用一个方法时,你也就是发送了一条消息给一个对象,在ruby中我们能够在运行时决定那个方法被调用。send 方法就是做这个的,他接受一个symbol为参数.

举个简单的例子,假设我们要写一个排序,我们想要使用不同的域作为比较的key。虽然我们这时可以用block,可是如果使用send的话,我们能有一个更优美的写法:

class 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


class 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.new("Hansel", 35, 69)
people << Person.new("Gretel", 32, 64)
people << Person.new("Ted", 36, 68)
people << Person.new("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!来调用:

class Foo   
private
def foo
"aa"
end
end
p Foo.new.__send!(:foo) # => nil
p Foo.new.send(:foo) #private method `foo' called for #<Foo:0xa89530> (NoMethodError)


2 特殊化一个单独的对象

在很多oo语言里,一个类的所有的对象共享相同的行为,类作为一个模板,当构造器调用时,制造一个拥有相同接口的对象。

ruby中我们可以改变一个对象运行时的状态。你可以给对象一个私有的,匿名的子类:所有的原始的方法都是可用的。由于联系到这个对象上的行为是私有的,因此它只发生一次。一件只发生一次的事叫做“singleton”,比如singleton 方法,和singleton类.

看下面的代码:

a = "hello"
b = "goodbye"

def b.upcase # create single method
gsub(/(.)(.)/) { $1.upcase + $2 }
end

puts a.upcase # HELLO
puts b.upcase # GoOdBye



加一个singleton 方法到一个对象,也就是创建了一个singleton 的类,然后这个类的父类是这个对象的类,如果你加多个方法到一个对象,这是你可以直接实现一个singleton 类:

b = "goodbye"

class << b

def upcase # create single method
gsub(/(.)(.)/) { $1.upcase + $2 }
end

def upcase!
gsub!(/(.)(.)/) { $1.upcase + $2 }
end

end

puts b.upcase # GoOdBye
puts b # goodbye
b.upcase!
puts b # GoOdBye


这里要注意的是,一些更“primitive”的对象(比如Fixnum),不能加singleton 方法,这是因为这些对象,不是存引用在内存中的。但是在ruby将来的版本,可能会实现这个。

在一些库的源码中,我们能看到这种代码:

class SomeClass

# Stuff...

class << self
# more stuff...
end

# ... and so on.

end


在一个类的体内,self 就指这个类自己,在这个类中的实例方法,其实也就是外面类的类方法:

class TheClass
class << self
def hello
puts "hi"
end
end
end

# invoke a class method
TheClass.hello # hi


使用这个技术的另一个原因是,可以创建一个类级别的帮助方法,然后我们就能在这个类的其他地方使用它了.

class MyClass

class << self

def accessor_string(*names)
names.each do |name|
class_eval <<-EOF
def #{name}
@#{name}.to_s
end
EOF
end
end

end

def initialize
@a = [ 1, 2, 3 ]
@b = Time.now
end

accessor_string :a, :b

end


o = MyClass.new
puts o.a # 123
puts o.b # Mon Apr 30 23:12:15 CDT 2001


extend 方法能够mix一个模块到一个对象:

module Quantifier

def any?
self.each { |x| return true if yield x }
false
end

def all?
self.each { |x| return false if not yield x }
true
end

end

list = [1, 2, 3, 4, 5]

list.extend(Quantifier)

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 创建一个带参数的类

假设我们想要创建一个多样的类,也就是说,可以通过控制类变量来控制它的多种状态:

class Terran

@@home_planet = "Earth"

def Terran.home_planet
@@home_planet
end

def Terran.home_planet=(x)
@@home_planet = x
end

#...

end


这样是可以的,这时如果我想要定义一些与Terran类似的类,你可能会马上想到是可以给这些类抽象出来一个超类就行了:

(注意,这里是错误的方法)
class IntelligentLife   # Wrong way to do this!

@@home_planet = nil

def IntelligentLife.home_planet
@@home_planet
end

def IntelligentLife.home_planet=(x)
@@home_planet = x
end

#...
end

class Terran < IntelligentLife
@@home_planet = "Earth"
#...
end

class Martian < IntelligentLife
@@home_planet = "Mars"
#...
end


当你调用Terran.home_planet时,在1.9中会打印出nil,在1.8中会打印出Mars.

为什么会这样?答案是class variables 不是真正的class variables 。他们不属于类,而是属于整个继承体系。class variables不能从父类所被复制,但是能够从父类所被共享。

我们可以剔除掉类变量在基类中的定义,可是这时我们定义的类方法就不能工作了。

这里有一个稍好一些的方法,使用了class_eval 方法:

class IntelligentLife

def IntelligentLife.home_planet
class_eval("@@home_planet")
end

def IntelligentLife.home_planet=(x)
class_eval("@@home_planet = #{x}")
end

#...
end

class Terran < IntelligentLife
@@home_planet = "Earth"
#...
end

class Martian < IntelligentLife
@@home_planet = "Mars"
#...
end


puts Terran.home_planet # Earth
puts Martian.home_planet # Mars


这个可以打印出我们想要的结果任何IntelligentLife里定义的实例变量,或者实例方法,都会被Terran 和 Martian所继承。

下面的方法可能是最好的方法,我们没有使用类变量,而是使用类实例变量:

class IntelligentLife
class << self
attr_accessor :home_planet
end

#...
end

class Terran < IntelligentLife
self.home_planet = "Earth"
#...
end

class Martian < IntelligentLife
self.home_planet = "Mars"
#...
end


puts Terran.home_planet # Earth
puts Martian.home_planet # Mars


这里我们打开了一个singleton class,定义了一个存取方法home_planet,两个子类调用他们自己的accessors 来设置变量.我们其实还可以给home_planet=方法设置为 private的。

这里其实还可以这样做:

module IntelligentLife
attr_accessor :home_planet
end

class Terran
class << self
include IntelligentLife
end
self.home_planet = "Earth"
#...
end

class Martian
class << self
include IntelligentLife
end
self.home_planet = "Mars"
#...
end


puts Terran.home_planet # Earth
puts Martian.home_planet # Mars



4 使用Continuations 来实现一个生成器

ruby的一个更抽象的特性就是continuation。这是一种控制非局部的跳转和返回的方法。一个continuation 对象存储着一个返回的地址,和一个上下文.他看起来很像c中的setjmp/longjmp ,可是他存储着更多的上下文.

Kernel 的方法callcc接受一个block,返回一个Continuation 对象。这个被返回的对象作为一个参数被传递进这个block.Continuation唯一的方法就是call,调用它将会引起一个非局部的返回,执行callc的block直到它的尾部。

其实Continuation很像游戏中的保存游戏的特性。最好的理解Continuation的方法,就是去看电影Run, Lola, Run (哈哈,就是罗拉快跑).

Continuation的最好的例子就是生成器,现在生成器已经是ruby的一部分了。

请看下面的使用生成器的Fibonacci numbers 的例子:

class Generator

def initialize
do_generation
end

def next
callcc do |here|
@main_context = here;
@generator_context.call
end
end

private

def do_generation
callcc do |context|
@generator_context = context;
return
end
generating_loop
end
def generate(value)
callcc do |context|
@generator_context = context;
@main_context.call(value)
end
end
end

# Subclass this and define a generating_loop

class 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 class...

fib = FibGenerator.new

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的性能不好,因为它保存了太多的状态和上下文...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值