Ruby 是动态语言,没有静态类型检查,同时也提供了很多 java 等静态语言无法提供的编程技巧。 本章主要专注于消除重复代码的技巧,通过用两种不同的方式对一段代码的重构来展示 Ruby 的强大功能,涉及的知识点比较少。
注 1: method_missing() 是 Kernel 中的一个实例方法,当 Ruby 找不到调用的方法时,它最后就会调用这个名为 method_missing() 的方法。
注 2: 移除一个对象中的所有方法,以便把它们转换成幽灵方法。
重构例子
# 原始代码
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
# 使用动态方法重构
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()动态定义方法
define_method(name) do
# 使用send()方法集中处理
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
define_component :mouse
define_component :cpu
define_component :keyboard
end
# 使用幽灵方法重构
class Computer
# 创建白板, 以免方法命名冲突
instance_methods.each do |m|
undef_method m unless m.to_s =~ /^__|method_missing|respond_to?/
end
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
# 在method_missing()中创建方法
def method_missing(name, *args)
super if !respond_to?(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
# 覆写respond_to?(),保证查询方法时返回正确结果
def respond_to?(method)
@data_source.respond_to?("get_#{method}_info") || super
end
end