Ruby 代碼重構(gòu)之旅

在《 Ruby 元編程》一書的第二章 method 中,通過一段代碼的重構(gòu),來展示 Ruby 的特性,如何以很少的代碼來達(dá)到我們最終的效果。

示例 Demo

源代碼:data_source.rb,其通過傳入工作站點(diǎn)的 id,用來獲取工作站點(diǎn)信息,如下:

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

調(diào)用代碼如下:

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

重構(gòu)

可以看到在 DS 類中,有很多重復(fù)的信息。第一步,首先將其抽象成一個(gè) 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

但是,抽象成這個(gè)類中,可以看到方法中對(duì) data_source 的使用還有點(diǎn)信息的冗余。這里可以對(duì)方法再以參數(shù)的形式調(diào)用,如下。

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 方法的參數(shù)指定一個(gè)方法的名稱和參數(shù),這樣對(duì)方法的調(diào)用就可以抽象在 component 方法中。
調(diào)用代碼:

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

PS: 這種動(dòng)態(tài)派發(fā)的這種特殊用法有時(shí)被稱為**模式派發(fā) (Pattern Dispatch),因?yàn)樗诜椒哪撤N模式來過濾方法。

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 方法,來在運(yùn)行時(shí)動(dòng)態(tài)地定義方法,也稱** 動(dòng)態(tài)方法 (Dynamic Method)**。

3. 內(nèi)省代碼的使用

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

這里通過使用正則表達(dá)式,來進(jìn)一步簡化方法的定義。使用 grep,當(dāng)滿足之后的正則表達(dá)式,則會(huì)定義相應(yīng)的方法。

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 方法(幽靈方法),是指在方法的調(diào)用過程中,若是在其類型中及其祖先鏈上找不到相應(yīng)的方法,則會(huì)在實(shí)例上調(diào)用 method_missing 方法(其屬于 Kernel 的一個(gè)實(shí)例方法,而所有的對(duì)象都繼承自 kernel 模塊)。這里通過重寫 method_missing 方法,來達(dá)到對(duì) data_source 中相應(yīng)的方法的動(dòng)態(tài)調(diào)用。

參考資料

Metaprogramming Ruby

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,551評(píng)論 19 139
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,049評(píng)論 0 9
  • 說明本次redis集群安裝在rhel6.8 64位機(jī)器上,redis版本為3.2.8,redis的gem文件版本為...
    讀或?qū)?/span>閱讀 15,621評(píng)論 3 9
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,008評(píng)論 25 709
  • hihihi ??!~~it is very glad to be here 工作中總會(huì)遇到那么多不如意,多少次內(nèi)心...
    郁影魚閱讀 261評(píng)論 0 0

友情鏈接更多精彩內(nèi)容