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

学无所长,一事无成

文章分类

全部博文(69)

文章存档

2015年(19)

2014年(14)

2013年(9)

2012年(17)

2010年(10)

我的朋友

分类: Python/Ruby

2012-07-31 10:36:08

前言

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() 方法则使用扁平作用域。

 

当前类及其特殊情况

考察如下代码 

  1. class MyClass
  2.     def method_one
  3.         def method_two; ‘hello’; end
  4.     end
  5. end

  6. obj = MyClass.new

  7. obj.method_one
  8. obj.method_two # => ‘hello’

1、因为定义 method_two 时,当前类的角色由 self 的类来充当,就是 MyClass ;所以我们得到如上结果

2、同理:当我们位于顶级作用域时。当前类是 Object --- 就是 main 对象的类,所以在顶级作用域中定义方法的时候,这个方法会成为 Object 类的实例方法。

 

当前类小结

1、类定义中,当前对象 self 就是正在定义的类(即当前对象就是当前类)。

2、Ruby 总是追踪当前类的引用。所有使用 def 定义的方法都会成为当前类的实例方法。

3、如果有一个类的引用,则可以使用 class_eval() 方法打开这个类。

 

类实例变量

看下面这个例子就一目了然了 


  1. class MyClass
  2.     @my_var = 1 # 这是一个类实例变量,因为此时 self 是 MyClass
  3.     
  4.     def self.read; @my_var; end # 这是一个类方法,所以可以访问类实例变量
  5.     def write;@my_var = 2; end # 这是实例方法,实例对象会作为 self传入该方法,因此只能访问该 obj 的实例变量。
  6.     def read; @my_var; end
  7. end 

  8. obj = MyClass.new
  9. obj.write
  10. obj.read # => 2
  11. MyClass.read # =>1 ;类实例变量,只能通过类方法访问,因为此时的 self 是 MyClass。

类变量

一句话,尽量避免使用类变量,多用类实例变量。

4.3 单件方法

演示:


  1. str = "just a regular string"
  2. def str.title?
  3. self.upcase == self
  4. end

  5. str.title? # => false
  6. str.methods.grep(/title?/) # => ["title?"]
  7. str.singleton_methods # => ["title?"]


关于类方法的真相

类方法的实质就是:它们是一个类的单件方法。实际上,如果比较单件方法的定义和类方法的定义,则会发现它们是一样的。


  1. an_object.a_method
  2. AClass.a_class_method


类宏

像 attr_accessor() 这样的方法称为类宏。虽然看起来像关键字,但它们只是普通的方法。

attr_accessor 是用 C语言写的,松本行弘给出了一个 Ruby 版的示例:


  1. class Module
  2.     def attr_accessor(*syms)
  3.         syms.each do |sym|
  4.             class_eval %{
  5.                 def #{sym}
  6.                     @#{sym}
  7.                 end                
  8.                 def #{sym}={val}
  9.                     @#{sym}=val
  10.                 end
  11.             }
  12.         end
  13.     end
  14. end


下面还有一个类似的例子:


  1. # File lib/spec/rake/spectask.rb, line 58
  2.       def self.attr_accessor(*names)
  3.         super(*names)
  4.         names.each do |name|
  5.           module_eval "def #{name}() evaluate(@#{name}) end" # Allows use of procs
  6.         end
  7.       end
应用类宏


将旧的方法通过类宏代理至新的方法


  1. class Book
  2.     def title # ...
  3.     def subtitle # ...
  4.     def lend_to(user)
  5.     puts "Lending to #{user}"
  6.     # ...
  7.     
  8.     def self.deprecate(old_method, new_method)
  9.         define_method(old_method) do |*args, &block|
  10.             warn "Warning: #{old_method}() is deprecated. Use #{new_method}()."
  11.             send(new_method, *args, &block)
  12.         end
  13.     end
  14.     
  15.     deprecate :GetTitle, :title
  16.     deprecate :LEND_TO_USER, :lend_to
  17.     deprecate :title2, :subtitle
  18. end
4.4 Eigenclass


单件方法到底存放在哪里??

对象本身是不可能的,对象的类也是不可能的。

揭秘 eigenclass

中文版这里翻译的很不好,看了英文的才明白,我修改如下:


  1. 当你向一个对象索要它的类时,Ruby 并没有告诉你全部真相。除了你所看到的类以外,对象还有一个此对象独有的,特殊的隐藏类。这个类称为该对象的 eigenclass。


对于这个特殊的 eigenclass 类,我们可以这样获取:


  1. obj = Object.new
  2. eigenclass = class << obj
  3.     self
  4. end
  5. eigenclass.class # => Class


Eigenclass 和 instance_eval() 方法

中文版这段翻译的也很糟,还是要看英文啊。我的翻译如下:


  1. 我们前面知道 class_eval() 会变换 self 和 当前类,而 instance_eval()会变换 self 。其实 instance_eval 它也会变换 当前类,只不过是变换到接受者的 eigenclass 类。


  1. 中文版将
  2. However, instance_eval( ) also changes the current class: it changes it to the eigenclass of the receiver.
  3. 翻译成 : 其实 instance_eval() 方法也会修改当前类:他会修改接受者的 eigenclass 。彻底晕菜!!
  4. 实在是误导人啊,增加一个‘到’,‘为’,‘成’也好啊,正确意思应该是这样:他会修改成接受者的 eigenclass。
  5. 修改一词也不妥,这里表达的更多是切换、变换的意思(有点类似于上下文切换),修改很容易误导人。


回顾方法查找
  1. # 为方便提取 eigenclass ,在Object 中定义一个方法
  2. class Object
  3.     def eigenclass
  4.         class << self; self; end
  5.     end
  6. end

  7. "abc".eigenclass # => #<Class:#<String:0x331df0>>
  8. #################################################
  9. # 构造两个类用于演示方法查找: D->C->Object
  10. #################################################
  11. class C
  12.     def a_method
  13.         'C#a_method()'
  14.     end
  15. end

  16. class D < C; end

  17. obj = D.new
  18. obj.a_method # => "C#a_method()"

  19. # 定义一个单件方法

  20. class << obj
  21.     def a_singleton_method
  22.         'obj#a_singleton_method()'
  23.     end
  24. end
  25. #################################################
  26. # 我们明白方法如何查找了:
  27. # obj#eigenclass->D->C->Object
  28. #################################################
  29. obj.eigenclass.superclass # => D

三种定义类方法的语法
1、中规中矩
  1. class MyClass
  2.     def self.my_method; end
  3. end
2、此种方式属于菜鸟
  1. def MyClass.my_other_method; end
3、使用 eigenclass 直击本质;有专家的风范
  1. class MyClass
  2.     class << self
  3.         def my_method; end
  4.     end
  5. end

Eigenclass  和继承 p125

eigenclass 的超类就是超类的eigenclass


由于有了这种组织方式,就可以在子类中调用父类的类方法了。


  1. D.a_class_method # => "C.a_class_method()"
备注:类也不过是个实例,类方法也不过是实例方法,因此可以继承。


大统一理论

1、只有一种对象:要么是普通对象,要么是模块

2、只有一种模块:可以使普通模块、类、eigenclass 或代理类

3、只有一种方法,它存在于一个模块中--通常是类中。

4、每个对象(包括类)都有自己“真正的类”--要么是普通类,要么是 eigenclass

5、除 BasicObject 外,每个类有且仅有一个超类。意味着从任何类只有一条向上直到 BasicObject 的祖先链。

6、一个对象的 eigenclass 的超类是这个对象的类(即 obj.class)。一个类的 eigenclass 的超类是这个类的超类的 eigenclass。用公式表述如下:


  1. obj.eigenclass.superclass == obj.class

  2. class D ... end

  3. D.eigenclass.superclass = D.superclass.eigenclass
7、当调用一个方法,Ruby 先向“右”一步,进入接受者真正的类,然后向上进入祖先链。



类属性 

通过 attr_accessor 可以给对象创建属性


  1. class MyClass
  2.     attr_accessor :a
  3. end

  4. obj = MyClass.new
  5. obj.a = 2
  6. obj.a             # =>2


要给类添加属性,怎么做呢?很简单,通过访问类的 eigenclass。(这里把类当成 Class 的一个实例)


  1. class MyClass
  2.     class << self
  3.         attr_accessor :c
  4.     end
  5. end

  6. MyClass.c = 'hello'
  7. MyClass.c        # => 'hello'
4.5 小测试:模块的麻烦


如何通过包含模块来定义类访法?


  1. module MyModule
  2.     def my_method; 'hello' ; end
  3. end

  4. class MyClass
  5.     class << self
  6.         include MyModule
  7.     end
  8. end

  9. MyClass.my_method     # => 'hello'
my_method() 方法时 MyClass 的 eigenclass 的一个实例方法,这样,my_method() 也是 MyClass 的一个类方法。这种技术称为类扩展。


类方法和 include()

类扩展,可以通过把模块混合到类的 eigenclass 中来定义类方法。类方法其实是单件方法的特例,因此你可以把这种技巧推广到任意对象。这种技术称为对象扩展。


  1. module MyModule
  2.     def my_method ; 'hello'; end
  3. end

  4. obj = Object.new
  5. class << obj
  6.     include MyModule
  7. end

  8. obj.my_method             # => 'hello'
  9. obj.singleton_methods     # [:my_method]
Ojbect#extend


如果你认为以上方法比较笨拙,还有一个简单办法。Ruby 中已经提供了一个 Object#extend 方法:


  1. module MyModule
  2.     def my_method; 'hello'; end
  3. end

  4. obj = Object.new
  5. obj.extend MyModule
  6. obj.my_method         # => 'hello'

  7. class MyClass
  8.     extend MyModule
  9. end

  10. MyClass.my_method     # => 'hello'


4.6 别名 P131

通过使用 alias 关键字,可以给 Ruby 方法取一个别名 


  1. class MyClass
  2.     def my_method; 'my_method()' ; end
  3.     alias :m :my_method
  4. end

  5. obj = MyClass.new
  6. obj.my_method    # => 'my_method()'
  7. obj.m            # => 'my_method()'
注意 alias 是关键字,所以其后的参数间是没有逗号分隔的。喜欢用逗号的,可以使用 Module#alias_method 方法,功能同  alias 完全一样。


环绕别名

1、给方法定义一个别名

2、重定义这个方法

3、在新的方法中调用老的方法


  1. module Kernel
  2.     alias gem_original_require require
  3.     
  4.     def require(path) # :doc:
  5.         gem_original_require path      
  6.   
  7.     rescue LoadError => load_error        
  8.         if load_error.message =~ /#{Regexp.escape path}\z/ and
  9.             spec = Gem.searcher.find(path) then
  10.             Gem.activate(spec.name, "= #{spec.version}" )
  11.             gem_original_require path
  12.         else
  13.             raise load_error
  14.         end
  15.     end
  16. end
两条警告


1、环绕别名是一种猴子补丁,你可能会破坏已有代码。

2、永远不要把一个环绕别名加载两次。

4.7小测验:打破数学规律(让 1+1 = 3)


  1. class Fixnum
  2.     alias :old_plus :+
  3.     
  4.     def +(value)
  5.         self.old_plus(value).old_plus(1)
  6.     end
  7. end

  8. class BrokenMathTest < Test::Unit::TestCase
  9.     def test_math_is_broken
  10.         assert_equal 3, 1 + 1
  11.         assert_equal 1, -1 + 1
  12.         assert_equal 111, 100 + 10
  13.     end
  14. end


收工

1、学习了类定义对  self 和当前类的影响

2、了解了单件方法和 eigenclass

3、学习了类实例变量 p106、类宏 p115、和环绕别名 p133

别忘了模块 

对类的所有描述,同样适用于模块。因此有 eigenclass ,同样也有 eigenmodule





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