yleo77 / Notes

Something about programming
MIT License
16 stars 3 forks source link

Metaprogramming Ruby Note - Part One #3

Open yleo77 opened 9 years ago

yleo77 commented 9 years ago

去年在看 Ruby on Rails 时非常喜欢它的简洁高效,后来也迷上了 Ruby 这门语言,感觉它就像一个魔法盒子一样,总是能看到很多得意于 Ruby 这门语言的自身大量特性而独有的一些解决问题的方式,所谓处处都是 Magic。

最近找来「Ruby 元编程」这本书,边看边做一些 Note 当做备忘。

元编程是什么,直白一点得说:就是编写能写代码的代码。程序世界的语言解释为

编写在运行时操纵语言构件的代码。

第一章 对象模型

Ruby 的 class 关键字和别的编程语言有个很大的区别点,它更像是一个作用域操作符而不是类型声明语句,核心作用是把你带到类的上下文中,让你可以在类中定义方法,通俗点就是当类不存在时定义类,当类存在时重新打开(不会覆盖原有的定义),并在原有类的基础上进行修改,这个也叫 打开类技术(Open Class)

对象有什么

区别:一个对象的实例变量存在于对象自身,方法存在于类中。所以对象共享方法,但不共享变量。

Class.instance_methods(false)    # 查看对象非继承来的方法

模块,类的区别

模块就是一组实例方法,而类则是一个增加了若干功能(superclass, new)的模块,实例化对象,类,模块这几个的关系可以在下面的图谱中看到。

如何选择这俩者,如果希望它被别处包含(或者当成命名空间),应该选择模块;如果希望它被实例化或者继承,应该选择类。

在类外面,可以这样引用常量:

MyModule::MyClass::MyConstant      
MyModule.constants     # 查看当前模块都有哪些常量

插播一个 Sidebar 的知识点,requireload 区别: 前者用来导入一个类库,后者用来执行一段代码。所以 load 有第二个参数可以控制执行代码的作用域。

对象,类,模块关于 superclass 和 class 引用关系

superclass_class

调用一个方法时,会发生什么

第一步,方法查找,第二步 执行这个方法。当执行的时候 ruby 需要一个 self 的东西。

接收者:调用方法所在的对象。祖先链: 从那个当前类移动到超类,依次直到 Object,Kernel,最后到达 BasicObject. 通过在一个 class 上调用 ancestors 可以查看到该类的祖先链。

Kernel 模块,比如看起来像关键字的 print 其实就是在 Kernel 里定义的。

Kernel.private_instance_methods.grep /^p/    

顶级上下文:当开始运行程序时,Ruby 会创建一个 main 的对象,self 就指向的 main。self 的角色,通常由最后一个接收到方法调用的对象来充当。

私有方法,好零散,私有方法只能被隐含接收者调用。也就是说不能在私有方法调用时传递一个接收者(这是规则,自然也就有打破规则的方法,下文会提到)。


第二章 方法

静态语言和动态语言,在前者中,在对象上调用一个不存在的方法,编译器这一关就不会通过;而在动态语言中,只有当真正执行到时才会检查,不存在时会报错。基于这两者的区别,所以这也是我为什么一直喜欢动态语言的原因之一:灵活。

关于动态派发和 send

obj.send 的形式调用对象方法,好处是对象调用的方法名可以当做一个参数,这样的意义是可以在运行期间来动态决定调用的是对象的哪一个方法。使用场景想想 javascript 中的 obj[prop] 就知道了。这个技术有个很 cool 的名字 动态派发(Dynamic Dispatch)

插播广告,关于符号,简单补充下两者区别:

其实没有太大意义的区别,两者也是可以进行互相转换(String#to_sym()和Symbol.to_s() )的。但是存在一个最佳实践,符号用于标识一个事物的名字,尤其是和元编程相关的名字,比如方法名。另外一点区别,符号是不可变的,而字符串是可以动态变换的,这也意味着两者的速度可能稍微有些区别。

回到这里, obj.send 调用时,根据方法名是否确定来选择是字符串 or 符号。

有了动态派发,少不了一个动态定义方法(Dynamic Method)的组合,就完美了。 Module#define_method

class MyClass
  define_method :m do |arg|
    arg * 3
  end
end

Object#send 有个杀手锏功能,这玩意破天荒得可以在外部 call 对象的私有方法,所以有时候会借用这个方法来打破封装。 Rails - ActiveRecord/validations.rb 把 ActiveSupport::Callbacks 模块扩充到ActiveRecord 上就借用了这个妙招。

强大的 method_missing

Object#send很酷炫,method_missing 叼炸天。

被 method_missing 处理的消息,从调用者的角度来看是没有区别的,但是接收者其实没有对应的方法,这个叫做幽灵方法(Ghost Method)

一个代码例子:

class MyClass

  def initialize
    @attributes = {}
  end

  def method_missing name, *args
    attribute = name.to_s
    if attribute =~ /=$/
      @attributes[attribute.chop] = args[0]
    else
      @attributes[attribute]
    end
  end
end

nick = MyClass.new
nick.name = 'nick'
nick.name

Rails 中的 ActiveRecord 就是使用了这个特性把数据库中的表和 model 进行了关联。强大,自然也会容易难以控制。一定需要注意,绝大多数,当不满足特定条件时需要通过 super 的形式把信息传递到 Kernel#method_missing 上,否则程序出错都不知道是哪里的问题了。

相关的两个点:

当幽灵方法和真实存在的方法冲突时,后者会胜出(废话嘛, 只有当方法不存在才会产生幽灵方法),基于这个情况,有时候可能需要一个白板(Blank State)类,BasicObject 就是了。

EOF