`
simohayha
  • 浏览: 1386570 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

ruby way之高级OOP特性之一

    博客分类:
  • ruby
阅读更多
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
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics