元编程:方法

这个章节主要是通过两种方式来解决静态语言中重复编码的问题,这两种方法分别是动态方法和幽灵方法。

1.待改写的冗余代码
#Ds模拟数据库
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
    info = @data_source.get_mouse_info(@id)
    puts info
  end

  def cpu
    info = @data_source.get_cpu_info(@id)
    puts 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)
    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)
    "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 self.define_component(name)
    define_method(name){
      info = @data_source.send "get_#{name}_info", @id
      puts info
    }
  end

  define_component :mouse
  define_component :cpu
end

cp = Computer.new(2, Ds.new)
cp.keyboard  # this is 2 keyboard information
3.3.使用内省方法
内省方法:在初始化的时候就对代码进行了优化,其中在初始化过程中使用如下代码:

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
    #内省
    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
      puts info
    }
  end
end

cp = Computer.new(2, Ds.new)
cp.keyboard  # this is 2 keyboard information
4. 幽灵方法
我自己的理解就是当一个对象调用方法的过程中,当这个方法不存在的时候,那么后台会报没有定义方法这个错误.
其实ruby的机制是当这个方法不存在的时候,对象会调用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,下面介绍另外一个关键词super
class Demo
  def method_missing(method, *args)
    return "this is the method missing" if method =~/cc/\
    #若与/cc/不匹配,那么执行super代码,返回到BasicObject类定义的method_missing
    super
  end
end

obj = Demo.new
obj.cc #this is the method missing
obj.dd #undefined method "dd"
5. 通过幽灵方法改写代码
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", args[0])
    puts info   
  end
end

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

上述的代码出现两个问题:
问题1:没有呈现出id的值,这个问题目前找不到比较合适的解决方法。
问题2:像比如keyboard这些ghost不是真正的方法,如下面的代码所示:
cp = Computer.new(2, Ds.new)
cp.respond_to?(:keyboard)   #=>false

解决问题2的方式可以通过猴子补丁的形式respond_to?方法来解决

class Computer
  def respond_to?(method)
    @data_source.respond_to("get_#{method}_info") || super
  end
  ##
cp = Computer.new(2, Ds.new)
cp.respond_to?(:keyboard)   #=>true
6. 幽灵方法中出现死循环
#method_missing方法出现了未被定义的方法或者变量
class Roulette
  def method_missing(name, *args)
    person = name.to_s.capitalize
    3.times do 
      number = rand(10) + 1
      puts "#{number}"  
    end
    #number是未被定义的变量
    "#{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 
7. 幽灵方法中出现方法冲突
#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
8. 使用白板
作用:避免方法冲突
两种方式:
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
9.使用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
10. 幽灵方法的性能考虑
#使用幽灵方法比使用普通方法要慢,因为在调用幽灵方法时,方法查找的路径一般要更长一些
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
性能比较
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容

  • Ruby是一门动态语言,动态创建与调用方法是其中一个体现。 动态方法 动态调用方法(动态派发) 动态调用方法,是指...
    未必治撕大叔阅读 1,088评论 2 8
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,259评论 25 707
  • 重生之爱 (文/亦浓) 过了午夜 是另一天了 可是, 竟如此不舍 纪念这个日子 十月十五号,八月二十六日 虔诚叩拜...
    开在夜里的花儿阅读 237评论 8 10
  • 上周,因为某天凌晨四点,我妈打来电话,把我惊醒,结果只是不小心按到,但是回完电话我已无法入睡。于是中午的...
    冰栀樱阅读 184评论 0 0
  • 向日葵生长的土壤下面, 埋葬了上帝的忧伤。 黑夜的尽处, 仿佛是魔鬼在欢唱。 我乘着不归的小船, 在死海里寻找方向...
    木叶飞飞阅读 297评论 0 1