Ruby 代码重构之旅

在《 Ruby 元编程》一书的第二章 method 中,通过一段代码的重构,来展示 Ruby 的特性,如何以很少的代码来达到我们最终的效果。

示例 Demo

源代码:data_source.rb,其通过传入工作站点的 id,用来获取工作站点信息,如下:

class DS
   def initialize # connect to data source...
   def get_mouse_info(workstation_id) # ...
   def get_mouse_price(workstation_id) # ...
   def get_keyboard_info(workstation_id) # ...
   def get_keyboard_price(workstation_id) # ...
   def get_cpu_info(workstation_id) # ...
   def get_cpu_price(workstation_id) # ...
   def get_display_info(workstation_id) # ...
   def get_display_price(workstation_id) # ...
   # ...and so on
end

调用代码如下:

ds = DS.new
ds.get_cpu_info(42) # => 2.16 Ghz
ds.get_cpu_price(42) # => 150
ds.get_mouse_info(42) # => Dual Optical
ds.get_mouse_price(42) # => 40

重构

可以看到在 DS 类中,有很多重复的信息。第一步,首先将其抽象成一个 Computer 的类:

class Computer

  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def mouse
    info = @data_source.get_mouse_info(@id)
    price = @data_source.get_mouse_price(@id)
    result = "Mouse: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end

  def cpu
    info = @data_source.get_cpu_info(@id)
    price = @data_source.get_cpu_price(@id)
    result = "Cpu: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end

  def keyboard
    info = @data_source.get_keyboard_info(@id)
    price = @data_source.get_keyboard_price(@id)
    result = "Keyboard: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end
  # ...
end

但是,抽象成这个类中,可以看到方法中对 data_source 的使用还有点信息的冗余。这里可以对方法再以参数的形式调用,如下。

1.使用 Object 类的 send 方法:

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end
  def mouse
    component :mouse
  end
  def cpu
    component :cpu
  end
  def keyboard
    component :keyboard
  end
  def component(name)
    info = @data_source.send "get_#{name}_info", @id
    price = @data_source.send "get_#{name}_price", @id
    result = "#{name.to_s.capitalize}: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end
end

send 方法的参数指定一个方法的名称和参数,这样对方法的调用就可以抽象在 component 方法中。
调用代码:

my_computer = Computer.new(42, DS.new)
my_computer.cpu # => * Cpu: 2.16 Ghz ($220)

PS: 这种动态派发的这种特殊用法有时被称为**模式派发 (Pattern Dispatch),因为它基于方法名的某种模式来过滤方法。

2.方法 define_method

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end
  def self.define_component(name)
    define_method(name) {
      info = @data_source.send "get_#{name}_info", @id
      price = @data_source.send "get_#{name}_price", @id
      result = "#{name.to_s.capitalize}: #{info} ($#{price})"
      return "* #{result}" if price >= 100
      result
    }
  end
  define_component :mouse
  define_component :cpu
  define_component :keyboard
end

使用 define_method 方法,来在运行时动态地定义方法,也称** 动态方法 (Dynamic Method)**。

3. 内省代码的使用

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
    data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
  end

  def self.define_component(name)
    define_method(name) {
      info = @data_source.send "get_#{name}_info", @id
      price = @data_source.send "get_#{name}_price", @id
      result = "#{name.capitalize}: #{info} ($#{price})"
      return "* #{result}" if price >= 100
      result
    }
  end
end

这里通过使用正则表达式,来进一步简化方法的定义。使用 grep,当满足之后的正则表达式,则会定义相应的方法。

4.method_missing 的使用

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end
  def method_missing(name, *args)
    super if !@data_source.respond_to?("get_#{name}_info")
    info = @data_source.send("get_#{name}_info", args[0])
    price = @data_source.send("get_#{name}_price", args[0])
    result = "#{name.to_s.capitalize}: #{info} ($#{price})"
    return " * #{result}" if price >= 100
    result
  end

end

method_missing 方法又称 ghost 方法(幽灵方法),是指在方法的调用过程中,若是在其类型中及其祖先链上找不到相应的方法,则会在实例上调用 method_missing 方法(其属于 Kernel 的一个实例方法,而所有的对象都继承自 kernel 模块)。这里通过重写 method_missing 方法,来达到对 data_source 中相应的方法的动态调用。

参考资料

Metaprogramming Ruby

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,923评论 18 139
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,768评论 0 9
  • 说明本次redis集群安装在rhel6.8 64位机器上,redis版本为3.2.8,redis的gem文件版本为...
    读或写阅读 15,144评论 3 9
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,282评论 25 708
  • hihihi !!~~it is very glad to be here 工作中总会遇到那么多不如意,多少次内心...
    郁影鱼阅读 244评论 0 0