类宏- 环绕别名
- singleton class
5.1 Class Definitions Demystified
5.11 Inside Class Definitions
美 [,dɛfɪ'nɪʃən]
Self关键字:Ruby的每一行代码都会在一个对象中被执行--这个对象就是所谓的当前对象,用self表示。
class and module 也是 object, so class also can be "self"
5.12 The Current Class 当前类
Ruby程序的任何位置,总会存在一个当前对象。因此就有一个当前类/模块存在。定义一个方法时,这个方法就会成为当前类的一个instance method.
1.在程序的顶层,当前类是Object,这是main对象的类。(所以在顶层定义方法会成为Object的private method。)
2.在一个方法中,当前类就是当前对象的类。在一个方法中嵌套另一个方法,新方法会定义在self所属的类中。
3.当用class/module关键字时,当前对象self转换为class/module.这个类也称为当前类。
class_eval
如何在不知道类名字的情况下,打开一个类? 使用Module#class_eval方法(module_eval)
例子,下面例子给String增加了一个新的方法m:
p self.class
p self.class
class_eval方法同时修改了self和当前类 。
class_eval方法和class比较:
- 灵活:可以对任何代表类的变量使用class_eval, 而class关键字只接收常量命名。
- scope: class_eval flatten the scope可以接收block外部scope的变量. 而class关键字是Scope Gate.
module_eval/class_eval 一样,module_exec/class_evec可以接收额外的代码块做参数。class_exec(arg...) {|var...| block } → obj
instance_eval 和 module_eval/class_eval 的 选择:
- instance_eval打开非类的对象,可以修改对象的方法和实例变量
- class_eval打开类,然后用def定义方法。
- 如果要打开的一个对象也是类/模块,选择可以准确表达你的目的的方法。
小结:
- Ruby总是追踪当前类/模块的引用,所有使用def的方法都成为当前类的实例方法。
- 类定义,当前类就是self--正在定义的类
- 如果有一个类的引用,可以用class_eval/module_eval打开这个类。(打开类:对已经存在的类进行动态修改)
5.13Class Instance Variables 类实例变量
Ruby解释权规定所有的实例变量都属于当前对象self。
类的实例变量,类的对象的实例变量。是两回事。
class Myclass
类变量:@@var,可以被子类和类的实例调用,为了避免以外,一般不使用类变量
5.3 Singleton Methods 单件方法
单件方法可以增强某个对象(类也是对象),是ruby最常见的特性。
Object#define_singleton_method:
define_singleton_method(symbol) { block } → symbol
#或者这么写,用Object#define_singleton_method定义单件方法
5.32 The truth about Class Methods
详细(5.4Singleton Classes)
AClass.a_class_method 这是在一个由常量引用的对象(类)上调用方法。
an_object.a_method 这是在一个由变量引用的对象上调用方法。
语法完全一样。
类方法的实质是: 它是一个类的单件方法。
object可以是对象引用,类,self。底层机制一样的。
def object.method
...
end
Duck Typing:鸭子类型。
Ruby这样的动态语言,对象的类型并不严格与它的类相关,“类型”只是对象能相应的一组方法。这也叫鸭子类型。 116页
5.33Class Macro类宏
什么是拟态方法?看起来像关键字或者别的,就是不像方法,但其实是方法。
puts, private, protect, attr_accessor, attr_read, attr_write(类宏)
什么是类宏?
普通的方法,看起来像关键字,其实是在类里定义好了。如attr_accessor, attr_read, attr_write。 类宏依靠类方法实现。
本例子:类宏用了类方法实现。
也使用了:
Dynamic method(Module#define_method):用于普获对旧方法的调用,并把调用转发给重命名的新方法。
Dynamic#dispatch(Module#send)
Kernel#warn: warn(msg, ...) → nil, 一般用于测试代码。
class Book
Warning: lend_to_user() is deprecated. Use lend_to()
Lending to bill5.4Singleton Class
5.41 the Mystery of Singleton Methods
对象的单件方法不存在对象里,也不存在它的类和超类中。
单件方法存在哪里?类方法是单件方法的特例,它又存在哪里?
答案:见5.42(绿色框)。
5.42 Singleton Classes Revealed
被隐藏起来,需要使用Object#singleton_class或class<<关键字 得到它
#两种显示对象的单件类的代码
由⬆️代码可知:
一个对象的单件方法(可以有多个单件方法) --存储在--> 一个单件类中;
因此,一个单件类也只有这么一个实例对象。
5.43 单件类在ancesstors中的位置。
p obj.singleton_class.ancestors
#=>[#<Class:#<D:0x00007fa9af03cc98>>, D, C, Object, Kernel, BasicObject]
obj->#obj->D->C->Object
Ruby模型对象的7条规则:
- 只有一种对象,要么是普通对象,要么是模块
- 只有一种模块,可以是一个普通模块,一个类或者一个单件类
- 只有一种方法,它存在于一个模块--通常是一个类中
- 每个对象(包括类)都有自己的“真正的类”--要么是一个普通类,要么是一个单件类
- 除了BasicObject类没有超类外,每个类有且只有一个祖先--要么是一个类,要么是一个模块。这意味着任何类只有一条向上的,直到BasicObject的祖先链
- 一个对象的单件类的超类是这个对象的类:一个类的单件类的超类是这个类的超类的单件类
- 调用一个方法,Ruby先向右迈一步进入接受者真正的类,然后向上进入祖先链。这是Ruby查找方法的方式。
类方法的语法:
class Myclass
def self.another_class_method; end
end
或者 (这是最好的选择✅,可以在class<<关键字中定义多个类方法,方便阅读代码。)
class Myclass
class << self
def yet_another_class_method; end
end
end
不建议:
def Myclass.a_class_method; end
单件类和instance_eval方法
class_eval方法会修改self,同时也就变更了当前类。instance_eval内修改self为调用它的receiver.
如果在instance_eval的{}中,定义一个def方法,当前类变为receiver的单件类, 这个方法成为receiver的singleton m
ethod.
所以,instance_eval也会变更当前类.不过Ruby程序员一般不用这个特点定义方法。
instance_eval的标准含义: 我想修改当前self.
“类属性”
Ruby对象没有属性的概念。所谓属性只不过是用拟态方法加上的一对儿读写的方法罢了。
这种简写的拟态方法,如attr_*也称为类宏Class Macro,其实就是普通的方法而已。
因此,类属性就是是这个类自己用的方法,类方法,放到这个类的单件类里.
5.5 Module Trouble
如何用包含模块的方式来定义一个类方法?
在模块中定义普通的实例方法,然后把模块放到类的单件类中,成为类方法。这称为Class Extension 类扩展
这种方法同样适用于对象,因为类方法本身就是单件方法的特例。所以可以把Module包含进对象的单件类中。这称为Object Extension.
Object#extend
类扩展和对象扩展使用普遍,所以打包代码做了一个extend方法。相当于快捷方式。
module Mymodule
5.6Method Wrappers
方法包装器
- Around Aliases
- Refinement Wrapper
- Prepended Wrapper
用途:有一个不能直接修改(在一个库中)或者修改的地方太多(怕漏掉)的Method, 我们希望为这个方法包装额外的功能。可以使用Method Wrappers.
Ruby提供alias关键字,为顶级作用域Object下提供帮助。
alias :new :old #⚠️ 没有逗号。
环绕别名
名字类似指针
- 给方法a定义一个别名b
- 重新定义a,此时我的理解b方法是过去的那个方法,a重新定义了所以是新的方法
- 在新的方法a中调用b方法,就叫环绕别名。
绝大部分Ruby操作符合实际上是方法,例如整数的+操作符是Fixnum#+方法的语法糖。
问题:重新定义+方法。让(x+y)的结果返回(x+y+1).?
解答:只能用环绕别名的方法,因为新的+方法依赖于旧的+方法。super方法不能重复用了
细化封装器 Refinement Wrapper
使用refine(mod){},配合using方法。作用域是:
- refine内部
- 从using开始到文件结束(如果是在顶层上下文中调用using)
- 或者从using开始到模块结束(如果是在模块中调用using)
module StringRefinement
Prepend Wrapper 下包含包装器(祖先链:当前类的下面)
#把包含的模块插入祖先链下方,而非上方。这意味着prepend方法包含的模块可以覆写该类的同名方法,同时可以通过super调用该类中的原始方法。
5.8小结
- 类定义对self(调用方法时默认的receiver)和当前类(定义方法时默认的所在地)的影响
- singleton_method, 单件类,类方法。 从新认识了对象模型(7条规则),和方法查找。
- 新的法术,类实例变量(类自己用不能继承),类宏(拟态简写),下包含包装器
- 模块就是类