Chinaunix首页 | 论坛 | 博客
  • 博客访问: 386234
  • 博文数量: 69
  • 博客积分: 1984
  • 博客等级: 上尉
  • 技术积分: 953
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-28 00:43
个人简介

学无所长,一事无成

文章分类

全部博文(69)

文章存档

2015年(19)

2014年(14)

2013年(9)

2012年(17)

2010年(10)

我的朋友

分类: Python/Ruby

2012-07-13 18:19:45

2.2 动态方法 p40

通过 send 向接收者发送消息,如:


  1. obj.my_method(3) #等价于
  2. obj.send(:my_method,3)


send 的第一个参数可以是字符串,也可以是符号。剩余参数(以及代码块)会直接传递给调用的方法。

 

符号:符号是不可变的。

字符串转换成符号: String#to_sym() 或者 String#intern()

符号转换成字符串: Symbol#to_s() 或者 Symbol#id2name()

 

来自 Camping 的例子

读取配置文件,调用方法

配置文件: admin : bill

转换后调用: conf.admin = ‘bill’

代码实现:


  1. if conf.rc and File.exists?( conf.rc )
  2.     YAML.load_file(conf.rc).each do |k,v|
  3.         conf.send(“#{k}=,v)
  4.     end
  5. end


动态定义方法

可以利用 Module#define_method() 方法定义一个方法,如:


  1. class MyClass
  2.     define_method :my_method do |my_arg|
  3.         my_arg * 3
  4.     end
  5. end

  6. obj = MyClass.new
  7. obj.my_method(2) # => 6


注: define_method 需要调用,才能动态创建方法。因此需要一定的动作才能触发。在 class 定义时是一个时机,因为在 class …. end 间是可以运行代码的(见上例)。另外我们可以放在 initialize 中,在初始化对象时,自动生成,这是一个比较恰当的时机。另外也可以放在其他的一些方法中。根据实际需要来选择了。

 思考:我们知道对象的方法是放在类中的,那么动态方法是放在那里的呢?显然不会是类中,因为动态生成的方法需要根据不同的实例而变化。

 2.3 method_missing() 方法

当一个对象就受到一个方法调用时,会到该对象的类中查询它的实例方法。找不到,则沿着祖先链向上搜索进入 Object ,并最终来到 Kernel 模块。最终还没找到,则在最初的接受者上调用 method_missing() 方法,这是Kernel 的一个实例方法,每个对象都会继承。尽管这是一个私有方法,我们还是可以通过 send() 来调用:

  1. nick.send :method_missing, :my_method

  2. => NoMethodError: undefined method ‘my_method’ for # <xxxxxxx>

覆写 method_missing() 方法

可以使你调用实际上并不存在的方法。


  1. class Lawyer
  2.     def method_missing(method,*args)
  3.         puts “you called: #{method } (#{args.join(,)})
  4.         puts “(you also passed it a block)if block_given?
  5.     end
  6. end

  7. bob = Lawyer.new

  8. bob.talk_simple(‘a’,’b’) do
  9.     #a block
  10. end


幽灵方法 

被 method_missing() 方法处理的消息,从调用者角度看,跟普通方法没有什么区别,但是实际上接受者并没有相对应的方法。这被称为一个有幽灵方法。

 

动态代理

通过封装对象的 method_missing() 方法来收集方法调用,并将其转发到被封装的对象上。 

代理:你可以使用 Ruby 的 delegate 库来快速得到一个使用的动态代理。
 

覆写 respond_to?() 方法

当调用一个幽灵方法是,通过 Object#methods() 是查不到的。通过 repond_to?() 查找该方法时,也会返回 false 。因此我们覆写 method_missing() 方法的同时,也需要覆写 respond_to?() 方法。 


  1. class Computer
  2.     def respond_to?(method)
  3.         @data_source.respond_to?(“get_#{method}_info”) || super
  4.     end
  5.     # ….
  6. end

  7. const_missing()


当引用一个不存在的常量时,Ruby 将把这个常量名作为一个符号传递给 const_missing() 方法 


  1. def Object.const_missing(name)
  2.     name.to_s.downcase.gsub(/_/,’ ‘)
  3. end

  4. MY_CONSTANT # =>”my constant”


 2.4 小测试:狩猎 Bug

当输入错误,或者作用域超出的时候。method_missing()会做出一些默认处理动作,或许这不是你想要的,而且这种错误由于覆写了 method_missing 很难发现。因此我们需要尽可能的限定 method_missing 的动作。(把无限责任变成有限责任,也可以说是白名单跟黑名单的区别,或者说是无罪推定跟有罪推定原则的区别:我默认 method_missing 是会犯错的,因此需要自证无罪,既约束代码,限定责任;)

 

2.5 关于 method_missing() 方法的更多内容

当方法冲突时

当一个幽灵方法和一个真实的方法发生名字冲突时,真实方法会胜出。为了安全起见,你应该在代理类中删除绝大多数继承来的方法。这就是所谓的白板类,他拥有的方法比 Object 类还要少(因此 Ruby 1.9.3 引入了 BasicObject)。

哈,这又是一个有罪推定。我个人总结:在设计对象模型时,我们采用无罪推定,充分信任每一个类或模块。而在具体设计某个类或模块时,要采用有罪推定,坚决不相信外部传入的任何东西,严格进行规制校验。如此才能创建良好封装,灵活可扩展,以及安全稳固的对象体系。

可以使用 Module#undef_method()方法来删除所有的(包括继承来的)方法;也可以使用 Module#remove_method() 方法,它只会删除接受者自己的方法。

 

性能方面的忧虑

总体来说,使用幽灵方法比普通方法要慢,因为需要更长的查找路径。但或许现有软件体系最慢的部分不是代码,而是IO,这个需要你权衡了。另外也有一些折中的方法,如第一次调用幽灵方法是,为它创建一个动态方法。 

保留方法

Ruby 中目前有两个保留方法: __send__() 和 __id__() 。 

BasicObject


  1. p BasicObject.instance_methods

  2. =>[:==,:equal?,:!,:!=,:instance_eval,:instance_exec,:__send__]


 我个人的偏好,除非万不得已,不要使用幽灵方法。六岁孩童使用千斤巨锤,非但不能发挥威力,反易自残。当然,牛人除外(牛人的特点就是喜欢剑走偏锋)。

阅读(1385) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~