Ruby元编程

####《Ruby 元编程》介绍了33中元编程技巧。

阅读本书可以学到对象类型、作用域、单件类、代码块等元编程概念,还能学到Rails的组织结构和工作原理,以及借助元编程编写领域专属语言。

元编程也有两面性:可以用元编程增强语言的功能,还可以创建领域专属语言;但是也可能陷入元编程的陷阱。元编程是一种容易让人迷惑的技术。

第一章 元这个字眼

元编程是编写能写代码的代码。

内省(introspection)检测自身属于什么类,有没有某个方法,自己的父类是谁等等。

元编程是编写能在运行时操作语言构件的代码。

1.3 元编程和Ruby Metaprogramming and Ruby
如果要操作语言构件,这些构件必须在运行时存在。
编写在运行时操作自身的代码,这称为动态元编程(dynamic metaprogramming),而代码生成器和编译器的那种方式称为静态元编程(static metaprogramming)。

第二章 星期一:对象模型

Ruby中,除了对象还有其他的语言构件,比如类(class)、模块(module)以及实例变量(instance variable)等。元编程(metaprogramming)操作的就是这些语言构件。
所有这些语言构件存在的系统称之为对象模型。对象模型是Ruby的灵魂。
2.1 打开类
从某种意义上说,Ruby的class关键字更像是一个作用域操作符,而不是类型声明语句。
猴子补丁:覆盖原有类的方法。
猴子补丁是个贬义词,使用它确实存在风险。如果误用它是危险的(比如补丁已存在的方法)。
有时候我们需要使用它,尤其是希望改造已有类库的时候,它很有用。
2.2 类的真相
Object#instance_variables ## 来查看对象包含的实例变量

与Java这样的静态语言不通,Ruby中对象的类和它的实例变量没有关系,当给实例变量赋值时,他们就突然出现了。因此,对于同一个类,你可以创建具有不同实例变量的对象。可以把Ruby中实例变量的名字和值理解为哈希表中的键/值对,每一个对象的键/值对都可能不同。

除了实例变量,对象还有方法。
Object#methods  ## 获得一个对象的方法列表
Object#instance_methods	   ## 获得一个对象的实例方法列表
Array#grep 	  ## 查询方法列表 obj.methods.grep(/my/) my开头的方法
如果可以撬开Ruby解释器并查看obj,你会注意到这个对象其实并没有真正存放一组方法。在其内部,一个对象仅包含它的实例变量以及一个对自身类的引用(因为每个对象都属于一个类,或者从面向对象的属于说,每个对象都是某个类的实例)。
共享同一个类的对象也共享同样的方法,因此方法必须存放在类中,而非对象中。
实例变量存放在对象中,而方法存放在类中。
一个对象的实例方法其实就是其类的实例方法。

总结一下:一个对象的实例变量存在于对象本身之中,而一个对象的方法存在于对象自身的类中。这就是同一个类的对象共享同样的方法,而不共享实例变量的原因。

Ruby对象模型最重要的知识:类本身也是对象。

Ruby编程的灵活性:其他的语言只允许你读取类的相关信息,而Ruby允许你在运行时修改这些信息。
Class.instance_methods(false)    # => 参数false在这里表示忽略继承的方法
Ruby的类继承自它的超类。
Array.superclass     # => Object
Object.superclass	 # => BasicObject
BasicObject.superclass # => nil
BasicObject是Ruby对象体系中的根节点,它只有少数几个方法。
Class类的超类是Module(模块),也就是说每个类都是一个模块。准确的说,类就是带有三个方法(new、allocate、superclass)的增强模块,这三个方法能让你按照一定的层次创建对象。
实际上,在Ruby中,类与模块这两个概念太接近了,完全可以用任意一个代表另一个。保留两个概念的主要原因就是为了获得代码的清晰性,让代码的意图显得更加明确。
如果希望把自己的代码包含(include)到别的代码中,就应该使用模块;如果希望某段代码被实例化或者被继承,就应该用类。

像普通的对象一样,类也可以通过引用来访问。
my_class = MyClass   # my_class和MyClass都是对同一个Class类的实例的引用,唯一区别在于,my_class是一个变量,而MyClass是一个常量。

换句话,就像类不过是对象而已,类名无非就是常量。

任何以大写字母开头的引用(包括类名和模块名)都是常量。
在代码中,常量像文件系统一样组织成树形结构,其中模块和类就像目录,而常量则像文件。像文件系统一样,只要不在同一个目录下,不同文件可以有相同的文件名。甚至可以像文件系统一样用路径来引用一个常量。
常量的路径用双冒号进行间隔。::
Module类还有一个实例方法和一个类型方法,名字都叫constants。Module#constants方法返回当前范围内的所有常量,就如同文件系统中的ls一样。
Module.constants方法返回当前程序中所有顶层的常量,其中也包括类名。
Module.nesting返回当前代码所在的路径。
对象和类的小结
什么是对象?对象就是一组实例变量外加一个指向其类的引用。对象的方法并不存在于对象本身,而是存在于对象的类中。在类中,这些方法被称为类的实例方法。
什么是类?类就是一个对象(Class类的一个实例)外加一组实例方法和一个对其超类的引用。Class类是Module类的子类,因此类也是一个模块。

load方法和require方法,load用于加载代码,require用于导入类库。require对每个文件只加载一次,而load方法在每次调用时都会再次运行所加载的文件。

2.4 调用方法时发生了什么?
  1. 找到这个方法;这个过程称为方法查找。
  2. 执行这个方法;为了做到这点,Ruby要用到一个称为self的东西。

接收者(receiver)和祖先链(ancestors chain)

接收者就是你调用方法所在的对象。
祖先链就是从一个类找到它的超类,然后再找到超类的超类,依次类推,直到找到BasicObject类(Ruby类体系结构的根节点)。在这个过程中,经历的类路径就是该类的祖先链。

一句话概括方法查找过程:Ruby首先在接收者的类中查找,然后再顺着祖先链向上查找,直到找到这个方法。

查找方法的方式:向右一步,再向上。

从Ruby2.0开始,还可以用另外一种方式把模块插入一个类的祖先链中:使用prepend方法。它的功能与include相似,不过这个方法会把模块插入祖先链中包含它的该类的下方,而不像include方法那样插入上方。每次include或prepend一个模块时,如果该模块已经存在于祖先链中,那么Ruby会忽略这个包含(include或prepend)指令。一个模块只会在一条祖先链中出现一次。
私有方法同时遵守两条规则:首先,如果调用方法的接收者不是自己,就必须明确指明接收者;其次,私有方法只能通过隐性的接收者调用。根据这两条规则,你只能在自身中调用私有方法。这条综合的规则称为私有规则。
可以调用从超类中继承来的私有方法么?可以,因为调用继承来的方法不用明确指明接收者。
Ruby程序开始运行时,Ruby解释器会创建一个名为main的对象作为当前对象。
细化:与打开类不同,细化在默认情况下并不生效。细化的作用范围只在该模块内部有效。细化和打开类的作用相似,区别在于细化不是全局性的。
细化只在两种场合有效:1.refine代码块内部;2.从using语句的位置开始到模块结束(如果是在模块内部调用using语句),或者到文件结束(如果是在顶层上下文中调用using)。
在细化有限的作用域范围内,其作用跟打开类(或者叫猴子补丁)是一样的。
细化一个类就像把一个补丁直接打到原有代码上一样。
Object.private_methods  # 获取私有方法
Object.methods == Object.public_methods
2.6 对象模型小结
  1. 对象由一组实例变量和类的引用组成。
  2. 对象的方法存在于对象所属的类中(对类来说是实例方法)。
  3. 类本身是Class类的对象。类的名字也是一个常量。
  4. Class类是Module的子类。一个模块基本上就是由一组方法组成的包。类除了具有模块的特性外,还可以被实例化(使用new方法),或者按照一定的层次结构来组织(使用superclass方法)。
  5. 常量像文件系统一样,是按照树形结构组织的。其中,模块和类的名字扮演目录的角色,其他普通的常量则扮演文件的角色。
  6. 每个类都有一个祖先链,这个链从每个类自己开始,向上直到BasicObject类结束。
  7. 调用方法时,Ruby首先向右找到接收者所属的类,然后向上查找祖先链,直到找到该方法或者达到链的顶端为止。
  8. 在类中包含一个模块(使用include方法)时,这个模块会被插入祖先链中,位置就在类的正上方;而使用prepend方法包含一个模块时,这个模块也会被插入祖先链中,位置在类的正下方。
  9. 调用一个方法时,接收者会扮演self的角色。
  10. 定义一个模块(或类)时,该模块会扮演self的角色。
  11. 实例变量永远被认定为self的实例变量。
  12. 任何没有明确指定接收者的方法调用,都当做是调用self的方法。
  13. 细化就像在原来的类上添加了一块补丁,而且它会覆盖正常的方法查找。此外,细化只在程序的部门区域生效:从using语句的位置开始,直到模块结束,或者直到文件结束。

第三章 星期二:方法

3.2 动态方法 Dynamic Methods

调用一个方法实际上是给一个对象发送一条消息。

# 动态派发
使用send方法调用方法:send(:my_method, 3)。方法名既可以使用字符串,也可以使用符号。剩下的参数和代码块会直接传递给调用的方法。
为什么使用send方法,这是因为在send方法里,想调用的方法名变成了参数,这样就可以在代码运行的最后一刻决定调用哪个方法。这个技巧称为动态派发(Dynamic Dispatch)。
符号和字符串:符号用于表示事务的名字——尤其是跟元编程相关的名字,比如方法名。符号是不可修改的,你可以修改字符串,但是不能修改符号。因此,符号特别适合表示方法名。谁都不希望方法名被修改吧?
不管怎样,符合和字符串是很容易相互转换的:to_sym/to_s

使用send方法比较漂亮的例子[50],Pry的例子。

Object#send方法功能非常强大,可以使用send调用任何方法,包括私有方法。
可以使用public_send方法,它和send方法相似,但它会尊重接收者的隐私。
# 动态定义方法
可以用Module#define_method()方法随时定义一个方法,只需要提供一个方法名和充当方法主体的块。
用Module#define_method方法代替def关键字定义方法的一个重要原因是:define_method方法允许在运行时决定方法的名字。
如果给String#grep方法传递一个块(block),那么对每个满足正则表达式的元素,这个块都会被执行。

动态定义方法比较漂亮的例子[54],Computer的例子。

3.3 method_missing方法
# 幽灵方法和动态代理
如果Ruby在继承链中找不到方法时,会在接收者对象上调用一个名为method_missing的方法。method_missing方法时BasicObject的一个私有实例方法,而所有对象都继承BasicObject类,所以它对所有的对象都可用。
覆写method_missing方法可以让你调用实际上并不存在的方法。
# 幽灵方法
被method_missing方法处理的消息,从调用者角度来看,跟普通方法没什么区别,而实际上接收者并没有对应的方法,这称为幽灵方法(Ghost Method)。
通常幽灵方法发挥的都是锦上添花的作用,不过也有些对象的功能几乎完全依赖于它。这些对象通常是一些封装对象,他们封装的可以是另一个对象、web服务或者是用其他语言编写的代码。这些对象通过method_missing方法收集方法调用,并把这些调用转发到被封装的对象上。
respond_to?(:my_method)  # 检查是否存在my_method方法
respond_to_missing?(:my_method)		# 检查是否存在my_method的幽灵方法(是个私有方法)

method_missing方法比较漂亮的例子[57],Hashie::Mash的例子。

method_missing方法比较漂亮的例子[59],Ghee的例子。

# 白板类(Blank Slate)
拥有极少方法的类称为白板类,它拥有的方法比object类还要少。
如果不特别指定超类,创建的类默认继承自Object类,它是BasicObject的子类。如果你需要一个白板类,可以直接从BasicObject类继承。
# 删除方法
删除一个方法有两种途径:一种是用Module#undef_method方法,另一种是用Module#remove_method方法。Module#undef_method会删除所有(包括继承而来的)方法;Module#remove_method只删除接收者自己的方法,而保留继承来的方法。

白板类比较漂亮的例子[68],builder的例子(xml)。

在可以使用动态方法的时候,尽量使用动态方法;除非必须使用幽灵方法,否则尽量不要使用它。

第四章 星期三:代码块(Blocks)

# 代码块
块可以用来控制作用域(scope)。作用域是变量和方法可用性范围。
可调用对象:块、proc和lambda。
代码块可以用大括号定义,也可以用do...end关键字定义。通常,只有一行的块用大括号,而多行的块用do...end。
只有调用一个方法时,才可以定义一个块。块会被直接传递给这个方法,该方法可以用yield关键字调用这个块。
块可以有自己的参数。
Kernel#block_given?方法可判断当前的方法调用是否包含块。
代码块不是浮在空中的,它不可能孤立地运行。运行代码需要一个执行环境:局部变量、实例变量、self等。
代码块包含代码,也包含一组绑定。
代码块是闭包的。换句话说,代码块可以获取局部绑定,并一直带着它们。
# 作用域 Scope
作用域到处都是绑定。
Ruby中作用域之间是截然分开的:一旦进入一个新的作用域,原先的绑定会被替换为一组新的绑定。
全局变量($)可以在任何作用域中访问。谁都可以修改全局变量,能不使用全局变量就尽量不要使用。可以使用顶级变量(@)代替全局变量。
# 作用域门
程序会在三个地方关闭作用域,同时打开一个新的作用域:1. 类定义 2. 模块定义 3. 方法。
三种情况关键字:class、module、def作为标志。每个关键字对应一个作用域门(Scope Gate)。
# 扁平化作用域:穿越作用域门
穿越作用域门方式:Class.new、Module.new、Module#define_method都可以。闭包上常用的法术。
如果两个作用域挤压在一起,它们就可以共享各自的变量,也可以成为扁平化作用域(Flat Scope)。
# 共享作用域
想在一组方法之间共享一个变量,但是又不希望其他方法访问这个变量,就可以把这些方法定义在那个变量所在的扁平作用域里。

在类定义和模块定义中的代码会立即执行。

嵌套文法作用域(nested lexical scopes)82,或者叫扁平化作用域(flattening the scope)

4.4 instance_eval方法
# instance_eval 方法
BasicObject#instance_eval方法,它在一个对象的上下文执行块。
传递给instance_eval方法的代码块称为上下文探针(Context Probe),因为它像是一个深入到对象中的代码片段,并可以对那个对象进行操作。
# instance_exec 方法
instance_eval的双胞胎兄弟instance_exec。它比instance_eval稍微灵活一点,允许代码块传入参数。
# 洁净室
洁净室只是一个用来执行快的环境。一个理想的化的洁净室是不应该有任何方法或者实例变量的(因为这样的话,其中的方法名和实例变量名有可能和代码块从其环境带来的名字冲突)。因此,BasicObject的实例往往用来充当洁净室,因为它是白板类,几乎没有任何方法。

打包代码,以后调用:块、proc(是由块转换来的对象)、lambda(proc的变种)、方法

4.5.1 Proc 对象
Proc就是由块转换来的对象。使用Proc.new创建一个Proc,以后就可以用Proc#call方法来执行这个由代码块转换的对象。这个技巧称为延迟执行(Deferred Evaluation)。
还有几种方法创建proc。Ruby有两个内核方法可以把块转换成Proc:lambda方法和proc方法。
还可以使用带刺的*lanbda操作符创建lambda。
4中代码块转换为Proc的方式:Proc.new、proc{}、lambda{}、->(x){}。
# & 操作符
代码块就像是方法额外的匿名参数。
&操作符的含义是:这是一个Proc对象,我想把它当成代码块来使用。去掉&操作符,就能再次得到一个Proc对象。
可以使用&操作符再把Proc转换成代码块。
yield是指代码块。

&操作符把Proc对象转换成代码块。

延迟执行的例子89

# Proc与Lambda的对比
用lambda方法创建的Proc与用其他方式创建的Proc有一些细微却重要的差别。用lambda方法创建的Proc称为lambda,而用其他方式创建的则称为proc。(可以用Proc#lambda?方法检测Proc是不是lambda。)
Proc与lambda的重要差别有两个。第一个与return关键字有关,另一个则与参数检验有关。
# Proc、Lambda和return
在lambda中,return仅仅表示从这个lambda中返回。
在proc中,return的行为则有所不同。它不是从proc中返回,而是从定义proc的作用域中返回。
# Proc、Lambda和参数数量
proc和lambda的第二个差别来自它们检查参数的方式。如果调用lambda时的参数数量不对,就会抛出ArgumentError错误;而proc则会把传来的参数调整成自己期望的参数形式。
整体而言,lambda更直观,因为它更像是一个方法。它对参数数量要求严格,而且在调用return时确实只是从代码中返回。可以把lambda作为第一选择,除非需要使用proc的某些特殊功能。
# Method 对象
Method对象类似于代码块或者lambda。实际上,可以通过Method#to_proc方法把Method对象转换为Proc。还可以通过define_method方法把代码块转换为方法。它们一个重要区别:lambda在定义它的作用域中执行(它是一个闭包),而Method对象会在它自身所在对象的作用域中执行。
# 自由方法
自由方法(unbound method)跟普通方法类似,不过它从最初定义它的类或者模块中脱离了。通过调用Method#unbind方法,可以把一个方法变成自由方法。也可以直接调用Module#instance_method方法获得一个自由方法。
可以把自由方法绑定到一个对象上,使之再次成为一个Method对象。具体做法是使用UnboundMethod#bind方法把UnboundMethod对象绑定到一个对象上。从某个类中分离出来的UnboundMethod对象只能绑定在该类及其子类的对象上,不过从模块分离出来的UnboundMethod对象在Ruby2.0之后不受这个限制。还可以把UnboundMethod对象传给Module#define_method方法,从而实现绑定。

Active Support 的例子95

# 可调用对象小结 Callable Objects Wrap-Up
可调用对象是可以执行的代码片段,而且它们有自己的作用域。可调用对象由以下几种:
1. 代码块(它们不是真正的对象,但它们是“可调用的”):在定义它们的作用域中执行。
2. proc:Proc类的对象跟代码一样,也在定义自身的作用域中执行。
3. lambda:也是Proc类的对象,但是跟普通的proc有细微的差别。它跟块和proc一样都是闭包,因此也在定义自身的作用域中执行。
4. 方法:绑定于一个对象,在所绑定对象的作用域中执行。它们也可以与这个作用域解除绑定,然后再重新绑定到另一个对象的作用域上。
4.6 编写领域专属语言(DSL)Writing a Domain-Specific Language

第一个领域专属语言 DSL 97

4.8 小结
1. 作用域门和Ruby 管理作用域的方式。
2. 利用扁平作用域与共享作用域让绑定穿越作用域。
3. 在对象的作用域中执行代码(通过instance_eval方法或instance_exec方法),在洁净室中执行代码。
4. 在代码块和对象(Proc)之间互相转换。
5. 在方法和对象(Method或UnboundMethod对象)之间相互转换。
6. 可调用对象(代码块、Proc、lambda及普通方法)之间的区别。
7. 编写自己的领域专属语言。

第五章 星期四:类定义

本章主要学习两种法术:类宏117可以用来修改类,环绕别名134可以在其他方法前后封装额外代码。为了最大限度的发挥这些法术的作用,本章还介绍单件类(singleton class),这是Ruby最优雅的特性之一。

# 提醒
1. 类不过是增强的模块,因此本章学到的所有知识也都可以应用于模块。所有讲”类定义“的内容,也是讲”模块定义“的。
2. 本章是本书最深奥的内容。
5.1 揭秘类定义
# 当前类
不管处在Ruby程序的哪个位置,总存在一个对前对象:self。同样,也总是有一个当前类(或模块)存在。定义一个方法时,那个方法将成为当前类的一个实例方法。
我们可以用self获取当前对象,但是Ruby并没有相应的方法获来获取当前类的引用。
  1. 在程序的顶层,当前类是Object,这是main对象所属的类。(这就是在顶层定义方法会成为Object实例方法的原因。)
  2. 在一个方法中,当前类就是当前对象的类。(试着在一个方法中用def关键字定义另外一个方法,你就会发现这个新方法定义在self所属的类中)
  3. 当用class关键字打开一个类时(或者用module关键字打开模块时),那个类称为当前类。
# class_eval 方法
Module#class_eval方法(它的别名是module_eval方法)会在一个已存在类的上下文执行一个快。
Module#class_eval方法和Object#instance_eval方法`85`截然不同。instance_eval方法只修改self,而class_eval方法会同时修改self和当前类。
Module#class_eval方法实际上比class关键字更灵活。可以对任何代办类的变量使用class_eval方法,而class关键字只能使用常量。另外,class关键字会打开一个新的作用域,这样将丧失当前绑定的可见性,而class_eval方法则使用扁平作用域(83)。这意味着可以引用class_eval代码块外部作用域中的变量。
通常我们用instance_eval方法打开非类的对象;而用class_eval方法打开类定义,然后用def定义方法。
# 当前类小结
1. Ruby解释器总是追踪当前类(或模块)的引用。所有使用def定义的方法都称为当前类的实例方法。
2. 在类定义中,当前类就是self——正在定义的类。
3. 如果你有一个类的引用,则可以用class_eval(或module_eval)方法打开这个类。

一个类实例变量只可以被类本身所访问,而不能被类的实例或者子类所访问。

类变量并不真正属于类——它属于类体系结构。

5.3 单件方法 Singleton Methods
# 使用单件方法
str = "just a regular string"
def str.title?
  self.upcase == self
end
str.title? 						# => false
str.methods.grep(/title?/)		# => [:title?]
str.singleton_methods			# => [:title?]
这段代码为str添加了一个title?方法。其他对象(即使是String对象)没有这个方法。只对单个对象生效的方法,称为单件方法(Singleton Method)。可以使用上面语法定义单件方法,也可以用Object#define_singleton_method方法来定义。
单件方法可以增强某个对象,也是Ruby最常见的特性。

类方法的实质是:一个类的单件方法。实际上,比较单件方法和类方法的定义,他们是一样的。

# 类方法真相
在静态语言(如Java)里,说对象的类型是T,是因为它属于类T(或是实现了接口T)。而在Ruby这样的动态语言里,对象的“类型”并不严格与它的类相关,“类型”只是对象能响应的一组方法。
def关键字定义单件方法的语法总是这样的:
def object.method
	# 这里是方法主体
end
object可以是对象引用、常量类名或者self。在这三种方式下,定义的语法可能看起来不同,但实际上,底层的机制都是一样的。

像attr_accessor这样的方法称为类宏(Class Macro)

# 类宏
类宏看起来很像关键字,但是它们其实只是普通的方法。只不过可以用在类定义里。
5.4 单件类 Singleton Classes

Ruby查找方法时先向右一步进入接收者的类,然后向上查找。

# 揭秘单件类
单件类又叫元类(metaclass)或本征类(eigenclass),单件类是它的正式名称。
像Object#class这样的方法会小心翼翼的把单件类隐藏起来。
单件类可以用Object#singleton_class方法或者class<<语法获得它。同时,每个单件类只有一个实例(这就是它们被称为单件类的原因),而且不能被继承。更重要的是,单件类是一个对象的单件方法的存活之所。
如果Ruby在单件类中找不到这个方法,那么它会沿着祖先链向上来到单件类的超类(这个对象所在的类)。
# 单件类和继承
讨论类、单件类和超类之间的关系。
单件类的超类就是超类的单件类。
这种组织方式可以让你在子类中调用父类的类方法。
Ruby对象模型有:类、单件类和模块,还有实例方法、类方法和单件方法。
Ruby对象模型七条规则
  1. 只有一种对象 —— 要么是普通对象,要么是模块。
  2. 只有一种模块 —— 可以是一个普通模块、一个类或者一个单件类。
  3. 只有一种方法,它存在于一个模块中 —— 通常是一个类中。
  4. 每个对象(包括类)都有自己的“真正的类” —— 要么是一个普通类,要么是一个单件类。
  5. 除了BasicObject类没有超类外,每个类有且只有一个祖先 —— 要么是一个类,要么是一个模块。这意味着任何类只有一条向上的、直到BasicObject的祖先链。
  6. 一个对象的单件类的超类是这个对象的类;一个类的单件类的超类是这个类的超类的单件类。
  7. 调用一个方法时,Ruby先向右一步进入接收者真正的类,然后向上进入祖先链。这就是Ruby查找方法的方式。

一个对象的单件类的超类是这个对象的类;一个类的单件类的超类是这个类的超类的单件类。

# 类方法的语法
因为类方式是生活在该类的单件类中的单件方法。有三种方法方式定义类方法:
def MyClass.a_class_methods; end
class MyClass
  def self.another_class_method; end
end
class MyClass
  class << self
    def yet_another_class_method; end
  end
end
第三种方法打开了该类的单件类,在那里定义方法。这种方式明确表明单件类才是类方法真正的所在之处。
# 单件类和instance_eval方法
instance_eval方法的标准含义是:我想修改self。
# 类属性
class MyClass
  class << self
    attr_accessor :c
  end
end
MyClass.c = 'it works!'
MyClass.c						# => 'it works!'
属性实际上只是一对方法。如果在单件类中定义了这些方法,那么他们实际上会成为类方法。
BasicObject的单件类的超类是class。
5.6 方法包装器 Method Wrappers

你将学习如用一个方法包装另外一个方法——有三种方式:环绕别名、细化封装器、下包含封装器。

# 方法别名 method alias
使用alias关键字,可以给Ruby方法取一个别名。
在alias_method 方法中,第一个参数是方法的新名字,第二个参数是方法的原始名字。可以用符号表示这些名字,也可以用没有前置冒号的普通字符串。
(Ruby还提供了alias关键字,可以替代Module#alias_method方法。当你在顶级作用域中修改方法时,需要使用alias关键字,因为这里的Module#alias_method方法不可用。)
重新定义方法时,并不真正修改这个方法。定义了一个新方法并把当前存在的这个方法名字跟它绑定。只要老方法存在一个绑定的名字,仍旧可以调用它。
# 环绕别名
通过以下三步来编写环绕别名:
1. 给方法定义一个别名。
2. 重定义这个方法。
3. 在新方法中调用老的方法。
环绕别名的一个缺点在于它污染了你的类,为它添加了一个额外的名字。要解决这个小问题,可以在添加别名之后,想办法把老版本的方法变成私有的。

Ruby的公有(public)和私有(private)实际上针对的是方法名,而非方法本身。

# 细化封装器(Refinement Wrapper)的作用范围只到文件末尾处(Ruby2.1中,是在模块定义的范围之内)。这让细化封装器比等价的环绕别名方法更安全,因为环绕别名是全局性的。
# 第三种方法包装技术(Module#prepend方法)
Module#prepend方法与include方法类似,但它会把包含的模块插在祖先链中该类的下方,而非上方。这意味着prepend方法包含的模块可以覆写该类的同名方法,同时可以通过super调用该类的原始方法。
可以称这种技术为下包含包装器(Prepended Wrapper)。与细化封装器相比,它不是一种局部化的技巧,但是一般认为它比细化封装器和环绕别名都更清晰。

第六章 星期五:编写代码的代码

# 工作计划
1. 使用eval方法编写一个名为add_checked_attribute的内核方法,用来为类添加一个最简单的经过校验的属性。
2. 重构add_checked_attribute方法,去掉eval方法。
3. 通过代码块来校验属性。
4. 把add_checked_attribute方法修改为名为attr_checked的类宏,它对所有类可用。
5. 写一个模块,通过钩子方法为指定的类添加attr_checked方法。
要学习两个知识点:一个是eval方法,另一个是钩子方法。

# Kernel#eval 方法
除了instance_eval和class_eval方法,现在来看看*eval家族的第三个成员 —— eval方法(它也是一个内核方法)。Kernel#eval方法在这三个方法中是最直接的。它没有使用代码块,而是直接使用包含Ruby代码的字符串,简称为“代码字符串(String of Code)”。Kernel#eval方法会执行字符串中的代码,并返回执行结果。
here文档(here document)一种特殊的字符串语法,也可以简称heredoc。
要想充分利用Kernel#eval方法,还应该对Binding类有所了解。
Binding就是一个用对象标识的完整作用域。可以通过创建Binding对象来捕获并带走当前的作用域。然后,可以通过eval方法在这个Binding对象所携带的作用域中执行代码。Kernel#binding方法可以用来创建Binding对象。
可以把Binding对象看做是比块更“纯净”的闭包,因为他们只包含作用域而不包含代码。
# irb的例子
irb的核心是一个简单的程序,它解析控制台(或文件)输入,再把每一行代码传给eval方法执行。这种类型的程序有时被称为代码处理器(Code Processor)。
irb调用eval方法时,会把当前处理代码所在的文件和行号作为参数传入。这样在异常堆栈中就可以看到正确的信息了。编写代码处理器时,这种处理调用堆栈的技巧特别有用。不过执行代码字符串时最好带上这些上下文参数,发生异常时方便查看。

eval方法只能执行代码字符串,不能执行代码块。instance_eval和class_eval除了执行代码块,也可以执行代码字符串。

字符串中的代码与代码块中代码没有太大的区别。代码字符串甚至可以像块那样访问局部变量:
array = ['a', 'b', 'c']
x = 'd'
array.instance_eval "self[1] = x"
puts array
请记住,能用代码块就尽量使用代码块。

# eval 方法的麻烦
存在代码注入的问题。

Ruby中像Kernel#load和Kernel#require这样的方法,它们接受文件名作为参数,然后执行那个文件中的代码。运行一个文件和运行一个字符串实际上没有太大的区别,这意味着load和require其实跟eval方法相似。尽管这些方法不属于*eval方法家族,也可以认为它们是远亲。

Ruby中好几个操作实例变量的方法,其中包括Object#instance_variable_get方法和Object#instance_variable_set方法。
6.7 钩子方法 Hool Methods
代码运行期间会发生很多事:类继承,模块混入类中,定义方法,删除方法等等。
inherited方法是class的一个实例方法,当一个类被继承时,Ruby会调用这个方法。默认情况下,Class#inherited方法什么也不做,但你可以覆写它的行为。像Class#inherited这样的方法称为钩子方法(Hook Method),因为它们像钩子一样,可以钩住一个特定的事件。
更多钩子方法:Module#included方法、Module#prepended方法。
通过覆写Module#extend_object 方法,还可以在模块扩展类时执行代码。通过覆写Module#method_added、mothod_removed或method_undefined方法,可以插入跟方法相关的事件代码。

这些钩子只对普通的实例方法(对象所属的类中的方法)生效,对单件方法(对象的单件类中的方法)则无效。如果想捕获单件方法的事件,则需要使用BasicObject中的singleton_method_added方法、singleton_method_removed方法和singleton_method_undefined方法。
不仅Class#inherited和Module#method_added这样特殊的方法可以作为钩子方法,绝大多数Ruby方法也可以通过某种方式实现钩子方法的功能。

extend方法会把ClassMethods模块中的方法包含到Request类的单件类中。
把类方法和钩子方法结合起来使用的技巧十分常见,Rails的源代码中就曾大量使用这种组合技巧,不过Rails现在已经转而使用其他技巧了。

第二部分 Rails中的元编程 (Metaprogramming in Rails)

第八章 准备Rails之旅

Rails是一个模型-视图-控制器(Model-View-Controller, MVC)框架,用于开发基于数据库的web应用。

# Rails
rails gem本身只包含Rake任务、代码生成器,以及用于绑定其他gem的胶水代码(glue code)等这样的辅助功能。那些需要绑定的gem才是最重要的。Rails中最重要的库有三个:activerecord(它把应用程序的对象映射到数据表中)、actionpack(处理web框架中的web部分)和activesupport(包含一些通用的工具类,比如时间运算和日志等)。

第九章 Active Record的设计 (The Design of Active Record)

Active Record库的用途主要是把Ruby对象映射到数据表中。这个功能称为对象关系映射(object-relational mapping),它结合了关系数据库(用于持久化)和面向对象编程(用于业务逻辑)的优点。

模块的包含:类包含一个模块后,模块中的方法通常会成为类的实例方法。

第十章 Active Support的Concern模块

ActiveSupport::Concern模块修改了Ruby的对象模型。它封装了“在包含我的类中添加类方法”这个功能,并且让这个功能很容易加入其它模块中。

ActiveRecord::Base包含Validations模块时,会发生三件事:
1. Validations的实例方法(如valid?方法)会成为Base的实例方法。这是模块包含的一般行为。
2. RUby在Validations模块上调用included这个钩子方法,并把ActiveRecord::Base作为它的参数。(included方法中有一个参数也叫base,但这个base跟Base类没有任何关系。)
3. 这个钩子方法使用ActiveRecord::Validations::ClassMethods模块对Base进行扩展,于是ClassMethods中的方法成为了Base的类方法。

这个技巧实在有点特殊,因此我不敢肯定它是否可以成为一种法术。我把它成为包含并扩展技巧(include-and-extend trick)。

ActiveSupport::Concern模块封装了包含并扩展技巧,并且解决了链式包含的问题。一个模块可以通过扩展Concern模块并定义自己的ClassMethods模块来实现包含并扩展的功能。

# Module#append_features方法
Module#append_features是Ruby的一个内核方法。与Module#included方法类似,该方法也是在包含一个模块时被调用。两者之间的区别在于:included方法时钩子方法,其默认实现是空的,只有覆写后才有内容。相反,append_features方法包含实际动作,用于检查被包含模块是否已经在包含类的祖先链上,如果不在,则将该模块加入祖先链。
# Concern小结
ActiveSupport::Concern是一个最小化依赖管理系统,它只用了一个单件方法以及短短几行代码就实现了这个功能。
Rails的类库基本上都是由模块拼装而成的。

元编程的目的不是让代码变得更聪明,而是让代码变得灵活。

第十二章 属性方法的发展

代码中究竟要处理多少种特殊情况?一种极端做法是从一开始就考虑到所有的情况,让自己的代码不留死角。我们称这种方式为“一次做好”。另一种极端做法是只处理一些很明显的问题,以后遇到特殊的情况时再处理。我们称这种方式为“渐进设计”。而实际的设计行为通常是在这两种方式之间寻找平衡。
尽量保持简单,只在必要时才增加复杂性。
根本就没有元编程,从来就只有编程。

拟态方法(Mimic Mehtod)

# 拟态方法
puts是拟态方法
private和protected这样的访问修饰符都是拟态方法,像attr_reader这样的类宏也是拟态方法。
# 空指针保护
a ||= []    =>    a || (a = [])
在布尔运算中,如果一个值不是nil或者false,它就被认为是true。
||操作符,如果第一个操作数是true,那么||会返回第一个操作数,而第二个操作数永远不会被执行;如果第一个操作数的值不是true,||操作符会执行并返回第二个操作数。

附录

# SelfYield
当给方法传入一个块时,你期望这个方法会通过yield对块进行回调。这种回调有一种有用的变形,即对象可以把自身传给这个块。
# Symbol#to_proc方法
Symbol#to_proc方法的目的就是用一种更简单的方式来替代一次调用代码块。
names = ['bob', 'bill', 'heather']
names.map(&:capitalize.to_proc)  # => ['Bob', 'Bill', 'Heather']
names.map(&:capitalize)				# => ['Bob', 'Bill', 'Heather']

Symbol#to_proc这被称为从符号到Proc(Symbol To Proc)的法术。

领域专属语言(Domain-Specific Languages)

# 使用领域专属语言
领域专属语言的对立面是像C++或Ruby这样的通用语言(general-purpose language,GPL)
现在有很多领域专属语言。Unix shell是一种用于黏合命令行工具的DSL:微软的VBA可以用来扩展Excel和其他Microsoft Office应用程序;make是一种专注于构建C程序的DSL;而Ant是Java平台的一种基于XML的make。
# 内部和外部领域专属语言
可以把Markaby称为内部领域专属语言,因为它存在于通用语言内部。相比之下,那些拥有独立解析器的语言(如make)常被称为外部领域专属语言。外部领域专属语言的另一个例子是Ant。尽管Ant解释器由Java编写,但是Ant语言跟Java语言截然不同。
Ruby比Java更容易改造,更适合你的具体需要。
Java和C的构建语言(分别是Ant和make)都是外部领域专属语言,而Ruby的标准构建语言(Rake)不过是Ruby的一个类库 —— 一个内部领域专属语言。
元编程是构建领域专属语言的砖瓦。
难点

沙盒149

Self Yield223

共享作用域84

符号到Proc225

###120页,第一段描述问题:你得到的类并非你看到的类,而是一个对象特有的隐藏类。这句话有问题。。。

150页,load和lrequire这样的方法。