前言
1、定义类实际上是在运行一段普通的代码
2、本章关注:类宏、环绕别名、单间类
4.1 类定义揭秘
1、可以在类定义中加入任何代码,因为我们实际上是在运行类定义。跟方法和块一样,类定义也会返回最后一条语句的值。
2、在类定义中,类本身充当了当前对象 self 的角色。
当前类
1、无论身处程序何处,我们知道总有一个当前对象:self。同样也总有一个当前类存在。当定义一个方法时,该方法将成为当前类的一个实例方法。
2、一般我们是通过 class 、module 关键字来判断当前类的,这需要知道类名或模块名。如果不知道类名,我们可以使用 class_eval()。
class_eval()
1、Module#class_eval() 方法(或其别名 module_eval() ) 会在一个已存在类的上下文中执行一个块:
2、Module#class_eval() 和 Object#instance_eval() 方法截然不同。 instance_eval() 方法仅仅会修改 self,而 class_eval() 方法会同时修改 self 和当前类。
3、通过修改当前类,class_eval() 实际上是重新打开了该类,就像 class 关键字所做的一样。
4、class 关键字会开启一个新的作用域,而 class_eval() 方法则使用扁平作用域。
当前类及其特殊情况
考察如下代码
-
class MyClass
-
def method_one
-
def method_two; ‘hello’; end
-
end
-
end
-
-
obj = MyClass.new
-
-
obj.method_one
-
obj.method_two # => ‘hello’
1、因为定义 method_two 时,当前类的角色由 self 的类来充当,就是 MyClass ;所以我们得到如上结果
2、同理:当我们位于顶级作用域时。当前类是 Object --- 就是 main 对象的类,所以在顶级作用域中定义方法的时候,这个方法会成为 Object 类的实例方法。
当前类小结
1、类定义中,当前对象 self 就是正在定义的类(即当前对象就是当前类)。
2、Ruby 总是追踪当前类的引用。所有使用 def 定义的方法都会成为当前类的实例方法。
3、如果有一个类的引用,则可以使用 class_eval() 方法打开这个类。
类实例变量
看下面这个例子就一目了然了
-
class MyClass
-
@my_var = 1 # 这是一个类实例变量,因为此时 self 是 MyClass
-
-
def self.read; @my_var; end # 这是一个类方法,所以可以访问类实例变量
-
def write;@my_var = 2; end # 这是实例方法,实例对象会作为 self传入该方法,因此只能访问该 obj 的实例变量。
-
def read; @my_var; end
-
end
-
-
obj = MyClass.new
-
obj.write
-
obj.read # => 2
-
MyClass.read # =>1 ;类实例变量,只能通过类方法访问,因为此时的 self 是 MyClass。
类变量
一句话,尽量避免使用类变量,多用类实例变量。
4.3 单件方法
演示:
-
str = "just a regular string"
-
def str.title?
-
self.upcase == self
-
end
-
-
str.title? # => false
-
str.methods.grep(/title?/) # => ["title?"]
-
str.singleton_methods # => ["title?"]
关于类方法的真相
类方法的实质就是:它们是一个类的单件方法。实际上,如果比较单件方法的定义和类方法的定义,则会发现它们是一样的。
-
an_object.a_method
-
AClass.a_class_method
类宏
像 attr_accessor() 这样的方法称为类宏。虽然看起来像关键字,但它们只是普通的方法。
attr_accessor 是用 C语言写的,松本行弘给出了一个 Ruby 版的示例:
-
class Module
-
def attr_accessor(*syms)
-
syms.each do |sym|
-
class_eval %{
-
def #{sym}
-
@#{sym}
-
end
-
def #{sym}={val}
-
@#{sym}=val
-
end
-
}
-
end
-
end
-
end
下面还有一个类似的例子:
-
# File lib/spec/rake/spectask.rb, line 58
-
def self.attr_accessor(*names)
-
super(*names)
-
names.each do |name|
-
module_eval "def #{name}() evaluate(@#{name}) end" # Allows use of procs
-
end
-
end
应用类宏
将旧的方法通过类宏代理至新的方法
-
class Book
-
def title # ...
-
def subtitle # ...
-
def lend_to(user)
-
puts "Lending to #{user}"
-
# ...
-
-
def self.deprecate(old_method, new_method)
-
define_method(old_method) do |*args, &block|
-
warn "Warning: #{old_method}() is deprecated. Use #{new_method}()."
-
send(new_method, *args, &block)
-
end
-
end
-
-
deprecate :GetTitle, :title
-
deprecate :LEND_TO_USER, :lend_to
-
deprecate :title2, :subtitle
-
end
4.4 Eigenclass
单件方法到底存放在哪里??
对象本身是不可能的,对象的类也是不可能的。
揭秘 eigenclass
中文版这里翻译的很不好,看了英文的才明白,我修改如下:
-
当你向一个对象索要它的类时,Ruby 并没有告诉你全部真相。除了你所看到的类以外,对象还有一个此对象独有的,特殊的隐藏类。这个类称为该对象的 eigenclass。
对于这个特殊的 eigenclass 类,我们可以这样获取:
-
obj = Object.new
-
eigenclass = class << obj
-
self
-
end
-
eigenclass.class # => Class
Eigenclass 和 instance_eval() 方法
中文版这段翻译的也很糟,还是要看英文啊。我的翻译如下:
-
我们前面知道 class_eval() 会变换 self 和 当前类,而 instance_eval()会变换 self 。其实 instance_eval 它也会变换 当前类,只不过是变换到接受者的 eigenclass 类。
-
中文版将
-
However, instance_eval( ) also changes the current class: it changes it to the eigenclass of the receiver.
-
翻译成 : 其实 instance_eval() 方法也会修改当前类:他会修改接受者的 eigenclass 。彻底晕菜!!
-
-
实在是误导人啊,增加一个‘到’,‘为’,‘成’也好啊,正确意思应该是这样:他会修改成接受者的 eigenclass。
-
-
修改一词也不妥,这里表达的更多是切换、变换的意思(有点类似于上下文切换),修改很容易误导人。
回顾方法查找
-
# 为方便提取 eigenclass ,在Object 中定义一个方法
-
class Object
-
def eigenclass
-
class << self; self; end
-
end
-
end
-
-
"abc".eigenclass # => #<Class:#<String:0x331df0>>
-
#################################################
-
# 构造两个类用于演示方法查找: D->C->Object
-
#################################################
-
class C
-
def a_method
-
'C#a_method()'
-
end
-
end
-
-
class D < C; end
-
-
obj = D.new
-
obj.a_method # => "C#a_method()"
-
-
# 定义一个单件方法
-
-
class << obj
-
def a_singleton_method
-
'obj#a_singleton_method()'
-
end
-
end
-
#################################################
-
# 我们明白方法如何查找了:
-
# obj#eigenclass->D->C->Object
-
#################################################
-
obj.eigenclass.superclass # => D
三种定义类方法的语法
1、中规中矩
-
class MyClass
-
def self.my_method; end
-
end
2、此种方式属于菜鸟
-
def MyClass.my_other_method; end
3、使用 eigenclass 直击本质;有专家的风范
-
class MyClass
-
class << self
-
def my_method; end
-
end
-
end
Eigenclass 和继承 p125
eigenclass 的超类就是超类的eigenclass
由于有了这种组织方式,就可以在子类中调用父类的类方法了。
-
D.a_class_method # => "C.a_class_method()"
备注:类也不过是个实例,类方法也不过是实例方法,因此可以继承。
大统一理论
1、只有一种对象:要么是普通对象,要么是模块
2、只有一种模块:可以使普通模块、类、eigenclass 或代理类
3、只有一种方法,它存在于一个模块中--通常是类中。
4、每个对象(包括类)都有自己“真正的类”--要么是普通类,要么是 eigenclass
5、除 BasicObject 外,每个类有且仅有一个超类。意味着从任何类只有一条向上直到 BasicObject 的祖先链。
6、一个对象的 eigenclass 的超类是这个对象的类(即 obj.class)。一个类的 eigenclass 的超类是这个类的超类的 eigenclass。用公式表述如下:
-
obj.eigenclass.superclass == obj.class
-
-
class D ... end
-
-
D.eigenclass.superclass = D.superclass.eigenclass
7、当调用一个方法,Ruby 先向“右”一步,进入接受者真正的类,然后向上进入祖先链。
类属性
通过 attr_accessor 可以给对象创建属性
-
class MyClass
-
attr_accessor :a
-
end
-
-
obj = MyClass.new
-
obj.a = 2
-
obj.a # =>2
要给类添加属性,怎么做呢?很简单,通过访问类的 eigenclass。(这里把类当成 Class 的一个实例)
-
class MyClass
-
class << self
-
attr_accessor :c
-
end
-
end
-
-
MyClass.c = 'hello'
-
MyClass.c # => 'hello'
4.5 小测试:模块的麻烦
如何通过包含模块来定义类访法?
-
module MyModule
-
def my_method; 'hello' ; end
-
end
-
-
class MyClass
-
class << self
-
include MyModule
-
end
-
end
-
-
MyClass.my_method # => 'hello'
my_method() 方法时 MyClass 的 eigenclass 的一个实例方法,这样,my_method() 也是 MyClass 的一个类方法。这种技术称为类扩展。
类方法和 include()
类扩展,可以通过把模块混合到类的 eigenclass 中来定义类方法。类方法其实是单件方法的特例,因此你可以把这种技巧推广到任意对象。这种技术称为对象扩展。
-
module MyModule
-
def my_method ; 'hello'; end
-
end
-
-
obj = Object.new
-
class << obj
-
include MyModule
-
end
-
-
obj.my_method # => 'hello'
-
obj.singleton_methods # [:my_method]
Ojbect#extend
如果你认为以上方法比较笨拙,还有一个简单办法。Ruby 中已经提供了一个 Object#extend 方法:
-
module MyModule
-
def my_method; 'hello'; end
-
end
-
-
obj = Object.new
-
obj.extend MyModule
-
obj.my_method # => 'hello'
-
-
class MyClass
-
extend MyModule
-
end
-
-
MyClass.my_method # => 'hello'
4.6 别名 P131
通过使用 alias 关键字,可以给 Ruby 方法取一个别名
-
class MyClass
-
def my_method; 'my_method()' ; end
-
alias :m :my_method
-
end
-
-
obj = MyClass.new
-
obj.my_method # => 'my_method()'
-
obj.m # => 'my_method()'
注意 alias 是关键字,所以其后的参数间是没有逗号分隔的。喜欢用逗号的,可以使用 Module#alias_method 方法,功能同 alias 完全一样。
环绕别名
1、给方法定义一个别名
2、重定义这个方法
3、在新的方法中调用老的方法
-
module Kernel
-
alias gem_original_require require
-
-
def require(path) # :doc:
-
gem_original_require path
-
-
rescue LoadError => load_error
-
if load_error.message =~ /#{Regexp.escape path}\z/ and
-
spec = Gem.searcher.find(path) then
-
Gem.activate(spec.name, "= #{spec.version}" )
-
gem_original_require path
-
else
-
raise load_error
-
end
-
end
-
end
两条警告
1、环绕别名是一种猴子补丁,你可能会破坏已有代码。
2、永远不要把一个环绕别名加载两次。
4.7小测验:打破数学规律(让 1+1 = 3)
-
class Fixnum
-
alias :old_plus :+
-
-
def +(value)
-
self.old_plus(value).old_plus(1)
-
end
-
end
-
-
class BrokenMathTest < Test::Unit::TestCase
-
def test_math_is_broken
-
assert_equal 3, 1 + 1
-
assert_equal 1, -1 + 1
-
assert_equal 111, 100 + 10
-
end
-
end
收工
1、学习了类定义对 self 和当前类的影响
2、了解了单件方法和 eigenclass
3、学习了类实例变量 p106、类宏 p115、和环绕别名 p133
别忘了模块
对类的所有描述,同样适用于模块。因此有 eigenclass ,同样也有 eigenmodule
阅读(1536) | 评论(0) | 转发(0) |