Ruby 用 Block 重构代码的几种方法

rocLv · 2014年10月13日 · 最后由 lonely21475 回复于 2014年10月31日 · 6041 次阅读
本帖已被设为精华帖!

Ruby 中 block 是非常重要的,也是非常特别的一部分内容。 很多代码的重构都可以通过 code block 得以一种优雅的方式实现。 根据《Ruby 最佳实践》中的内容,我简单做一些小结。

首先,最常见的就是用于遍历。

(1..5).to_a.map { |i| i * 2 }

class SortedList

  def initialize
    @data = []
  end

  def <<(element)
    (@data << element).sort!
  end

  def each
    @data.each { |e| yield(e) }
  end

end

其次,用于在方法的预处理和后处理操作中插入代码

File.open("some.txt") { |f| f << "some words" }

有时也用于临时提升变量的可见范围(scope)(在元编程中比较常见)

"This is a string".instance_eval do
  "O hai, can has reverse? #{ revers }. kthxbye"
end

#=> "O hai, can has reverse? gnirts a si sihT. kthsbye"

最后,就是作为模板

>> foo = Hash.new { |h, k| h[k] = [] }
=> {}
>> foo[:bar]
=> []
>> foo[:bar] << 1 << 2 << 3
=> [1, 2, 3]

用 Block 重构代码的几个范例

用 block 来重构 Pre 和 Post 处理过程

require "socket"

class Client

  def initialize(ip = "127.0.0.1", port = 3333)
    @ip, @port = ip, port
  end

  def send_message(msg)
    socket = TCPSocket.new(@ip, @port)
    socket.puts(msg)
    response = socket.gets

  ensure
    socket.close if socket
  end

  def receive_message
    socket = TCPSocket.new(@ip, @port)
    response = socket.gets

  ensure
    socket.close if socket
  end


end

这个代码明显不符合 DRY 的风格,所以利用 block 来重构一下:

require "socket"

class Client

  def initialize(ip = "127.0.0.1", port = 3333)
    @ip, @port = ip, port
  end

  def send_message(msg)
    connection do |socket|
      socket.puts(msg)
      socket.gets
    end
  end

  def receive_message
   connection { |socket| socket.gets }
  end

  private

  def connetcion
    socket = TCPSocket.new(@ip, @port)
    yield(socket)

  ensure
    socket.close if socket
  end

end

if __FILE__ == $0
  client = Client.new

  ["Hello", "My name is Greg", "Goodbye"].each do |msg|
    response = client.send_message(msg)
    puts response
  end
end

#输出以下内容
#Hello from server at Wed Jul 23 16:15:37 ...
#Nice to meet you Greg!
#Goodbye from server at Wed Jul 32 ...

以上代码明显简洁了很多

用于动态回调

require 'socket'

class Server

  def initialize(port = 3333)
    @server = TCPServer.new("127.0.0.1", port)
    @handlers = {}
  end

  def handle(pattern, &block)
   @handlers[pattern] = block
  end

  def run
    while session = @server.accept
      msg = session.gets
      match = nil

      @handlers.each do |pattern, block|
        if match = msg.match(pattern)
          break session.puts(block.call(match))
        end

        unless match
          session.puts "Server received unknown message: #{msg}"
        end
      end

    end
  end

end

if __FILE__ == $0
  server = Server.new

  server.handle(/hello/i){ "Helllo from server at #{Time.now}" }
  server.handle(/goodbye/i){ "Goodbye from server at #{Time.now}" }
  server.handle(/name is (\w+)/){ |m| "Nice to meet you #{m[1]}!" }

  server.run
end

用于简化接口


 server = Server.new

server.handle(/hello/i){ "Helllo from server at #{Time.now}" }
server.handle(/goodbye/i){ "Goodbye from server at #{Time.now}" }
server.handle(/name is (\w+)/){ |m| "Nice to meet you #{m[1]}!" }

server.run

#这段代码可以简化为以下版本
Server.run do
  handle(/hello/i){ "Helllo from server at #{Time.now}" }
  handle(/goodbye/i){ "Goodbye from server at #{Time.now}" }
  handle(/name is (\w+)/){ |m| "Nice to meet you #{m[1]}!" }
end

#class Server修改如下
class Server
  #other methods same as before

  #声明为类方法
  def self.runport=3333, &block)
    server = Server.new(port)
    #使用instance_eval和&block来重构代码
    server.instance_eval(&block)
    server.run
  end
end


备注

  • 假如要创建一个需要遍历元素的集合类,在 Emuerable 上的基础建立,即 include Enumarable
  • 假如两个方法只有中间部分不同,创建一个 yield block helper 避免代码重复
  • 使用&block,你可以把代码块保存在一个变量中
  • 使用&blockinstance_eval组合可以在任意对象的上下文中执行代码块

good job

Ruby 有 instance_eval,在抽象代码块时,比其他只支持闭包的语言有优势。

干干的。

发现点小错误,instance_eval 写成 instace_eval

revers = "good morning,erveryone!".reverse!  # add this maybe better
"This is a string".instace_eval do    # should use instance_eval
  "O hai, can has reverse? #{ revers }. kthxbye"
end

一碰到底层代码就有点蒙圈了,求相关指导教材~

#6 楼 @lonely21475《ruby 元编程》就是这方面的经典书籍,需要一定的编程基础。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册