1.待改写的冗余代码

2.动态派发和动态方法

3.动态方法改写冗余代码

3.1.使用动态派发和代理

3.2.添加动态方法

3.3.使用内省方法

4.幽灵方法

4.1幽灵方法示例:死循环

4.2.通过幽灵方法改写代码

4.3.method_missing出现方法冲突

5.使用白板

5.1.使用白板和method_missing对代码进行改写

6.method_missing的性能测试

1.待改写的冗余代码

##Ds模拟数据库
class Ds
  def get_mouse_info(id)
    return "this is #{id} mouse information"
  end

  def get_cpu_info(id)
    return "this is #{id} cpu information"
  end
end

class Computer
  def initialize(id, data_source)
    @id = id
    @data_source = data_source
  end

  def mouse
    info = @data_source.get_mouse_info(@id)
    p info
  end

  def cpu
    info = @data_source.get_cpu_info(@id)
    p info
  end
end

cp = Computer.new(1, Ds.new)
cp.mouse

2.动态派发和动态方法

#动态派发:在代码运行期间,直到最后一刻才决定调用哪个方法,比如使用send方法.
#动态方法:在运行时定义方法的技术,比如define_method.

#动态派发实例
class MyClass
  def my_method(my_arg) 
    puts my_arg*2 
  end
end
obj = MyClass.new
obj.my_method(3) #=>6
#当你调用一个方法时,实际上是给一个对象发送了一条消息代码如下所示:
obj.send(:my_method, 3) #=>6
obj.send("my_method", 6) #=>6
obj.send(my_method, 6) #=> undefined local variable or method

#动态方法实例
class MyClass
  define_method :my_method do |my_arg|
    puts my_arg*3
  end
end
obj = MyClass.new
obj.my_method(2)

#总结:上面的两段代码分别显示了方法定义和方法调用的非常规形式:
1. 使用define_method代替def关键词,实现类定义的作用.
2. 使用“.”来进行方法调用,把方法名作为参数

3.动态方法改写冗余代码

3.1.使用动态派发和代理

class Ds
  def get_mouse_info(id)
    puts "this is #{id} mouse information"
  end

  def get_cpu_info(id)
    puts "this is #{id} cpu information"
  end
end

class Computer
  def initialize(id, data_source)
    @id = id
    @data_source = data_source
  end

  def mouse
    component :mouse
  end

  def cpu
    component :cpu
  end

  def component(name)
    #使用动态派发的原因是方法可以作为参数被调用,而obj.method需要直接指定方法名称
    info = @data_source.send "get_#{name}_info", @id
    puts info
  end
end

cp = Computer.new(1, Ds.new)
cp.mouse

3.2.添加动态方法

class Ds
  def get_mouse_info(id)
    return "this is #{id} mouse information"
  end

  def get_cpu_info(id)
    return "this is #{id} cpu information"
  end
end

class Computer
  def initialize(id, data_source)
    @id = id
    @data_source = data_source
  end

  def self.define_component(method)
    define_method(method) do
      info = @data_source.send("get_#{method}_info", @id)
      puts info
    end
  end

  #通过执行方法,然后定义方法
  define_component :mouse 
  define_component :cpu
end

cp = Computer.new(1, Ds.new)
cp.mouse

3.3.使用内省方法

#内省方法:在初始化的时候就对代码进行了优化,其中在初始化过程中使用如下代码
class Ds
  def get_mouse_info(id)
    return "this is #{id} mouse information"
  end

  def get_cpu_info(id)
    return "this is #{id} cpu information"
  end
end

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

  def self.define_component(method)
    define_method(method) do
      info = @data_source.send("get_#{method}_info", @id)
      puts info
    end
  end
end

cp = Computer.new(1, Ds.new)
cp.mouse

4.幽灵方法

#当一个对象没有存在方法时,会报错,但定义幽灵方法时候,方法不存在时会调用幽灵方法
#幽灵方法是method_missing,这个方法是BasicObject类的实例方法.

#代码示例
class Demo
end
obj = Demo.new
obj.cc #undefined method "cc"

#使用method_missing方法对代码进行改写.
class Demo
  def method_missing(method, *args)
    puts "this is the method missing"
  end
end
obj = Demo.new
obj.cc #this is the method missing

#上面的代码其实用到的是monkey patch(在不改变源码的情况下,改变方法的功能)
class Demo
  #method_missing接受两个参数,第一个是方法名,第二个是参数,而且必须是可变参数
  def method_missing(method, *args)
    return "this is the method missing" if method =~/cc/
    #若与/cc/不匹配,那么执行super代码
    super
  end
end

obj = Demo.new
puts obj.cc #this is the method missing
obj.dd #undefined method "dd"

4.1幽灵方法示例:死循环

#method_missing方法出现了未被定义的方法或者变量
class Roulette
  def method_missing(name, *args)
    person = name.to_s.capitalize
    3.times do 
      number = rand(10) + 1
      puts "#{number}"  
    end
    #错误原因,块中变量在块外面就失去意义
    "#{person} got a #{number}"
  end
end

number_of = Roulette.new
puts number_of.bob
puts number_of.frank 

#代码修正
class Roulette
  def method_missing(name, *args)
    person = name.to_s.capitalize
    super unless %w[Bob Frank Bill].include? person
    number = 0
    3.times do 
      number = rand(10) + 1
      puts "#{number}"  
    end
    "#{person} got a #{number}"
  end
end

number_of = Roulette.new
puts number_of.Bob
puts number_of.frank

4.2.通过幽灵方法改写代码

#通过method_missing来改写代码
class Ds
  def get_mouse_info(id)
    "this is #{id} mouse information"
  end

  def get_cpu_info(id)
    "this is #{id} cpu information"
  end
end

class Computer
  def initialize(id, data_source)
    @id = 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", @id)
    puts info
  end
end

cp = Computer.new(2, Ds.new)
cp.mouse  # this is  mouse information
#上述的代码的问题:mouse这些ghost不是真正的方法,如下面的代码所示:
cp = Computer.new(2, Ds.new)
cp.respond_to?(:mouse)   #=>false

#通过猴子补丁的形式respond_to?方法来解决
def respond_to?(method)
  @data_source.respond_to?("get_#{method}_info") || super
end

#结合method_missing和respond_to?的完整代码
class Ds
  def get_mouse_info(id)
    "this is #{id} mouse information"
  end

  def get_cpu_info(id)
    "this is #{id} cpu information"
  end
end

class Computer
  def initialize(id, data_source)
    @id = 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", @id)
    puts info
  end

  def respond_to?(method)
    @data_source.respond_to?("get_#{method}_info") || super
  end
end
cp = Computer.new(2, Ds.new)
cp.respond_to?(:mouse)   #=>true

4.3.method_missing出现方法冲突

#method_missing和真实存在方法冲突时,执行真实方法,忽略method_missing方法
class Ds
  def get_display_info(id)
    "this is #{id} display information"
  end
end

class Computer
  def initialize(id, data_source)
    @id = 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])
    puts info   
  end
end

cp = Computer.new(2, Ds.new)
cp.display#  <#Computer: xxxxxx>

#display是Object的实例方法:
Object.instance_methods.grep(/display/) #=>display

5.使用白板

#白板的作用:避免方法冲突,主要通过下面两种方法来使用白板
1、Module#undef_method方法来删除所有方法,包括继承来的方法
2、Module#remove_method方法来删除接受者自己的方法,而保留继承来的方法。

#通过下面的语句可以把Computer类编程一个白板
class Computer
  instance_methods.each do |m|
    undef_method m unless m.to_s =~ /method_missing|respond_to?/
  end
#……
#……
end

#上面的代码会报两个warning:
 warning: undefining `object_id' may cause serious problems
 warning: undefining `__send__' may cause serious problems

#为了安全起见,这两个保留方法不能够删除,因此改动上面的代码:
class Computer
  instance_methods.each do |m|
    undef_method m unless m.to_s =~ /^__|object_id|method_missing|respond_to?/
  end
#……
#……
end

5.1.使用白板和method_missing对代码进行改写

class Ds
  def get_mouse_info(id)
    "this is #{id} mouse information"
  end

  def get_cpu_info(id)
    "this is #{id} cpu information"
  end
end

class Computer
  instance_methods.each do |m|
    undef_method m unless m.to_s =~ /^__|object_id|method_missing|respond_to?/
  end

  def initialize(id, data_source)
    @id = 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])
    puts info   
  end

  def respond_to?(method)
    @data_source.respond_to("get_#{method}_info") || super
  end 
end

cp = Computer.new(2, Ds.new)
cp.keyboard  # this is  keyboard information

6.method_missing的性能测试

#使用幽灵方法比使用普通方法要慢,因为在调用幽灵方法时,方法查找的路径一般要更长一些
class String
  def method_missing(method, *args)
    method == :ghost_reverse ? reverse : super
  end
end

require 'benchmark'

Benchmark.bm do |b|
  b.report 'Normal method' do
    1000000.times{ "abc".reverse }
  end

  b.report 'Ghost method' do
    1000000.times{ "abc".ghost_reverse }
  end
end

results matching ""

    No results matching ""