Class Defintions Demystified

跟其他的面向对象的编程语言不同,在 Ruby 中,当使用 class 关键字时,并非是在指定对象未来的行为方式,相反,实际上是在运行普通的代码。事实上,可以在类定义中放入任何代码。而且要牢记,类只是一个增强的模块,因此我们在此学到的任何东西都可以应用于模块。当看到关于“类定义”内容的时候,也可以把它认为是“模块定义”。

  class MyClass
    puts 'Hello!'
  end
  #=> Hello!

跟方法和块一样,类定义也会返回最后一条语句的值。

The Current Class

不管处在 Ruby 程序的哪个位置,总存在一个 当前对象:self。类似的,也总是有一个 当前类 或模块存在。当定义一个方法时,该方法将成为当前类的一个实例方法。

尽管可以用 self 获得当前对象,但 Ruby 并没有相应的方式来获得当前类的引用。然而,跟踪当前类并不难,每当通过 class 关键字来打开一个类,这个类就成为当前类。

class 关键字有一个限制:它需要一个类的名字。但是在某些情况下,可能我们并不知道要打开的类的名字。设想一个以类为参数的方法,它给这个类添加了一个新的实例方法:

  def add_method_to(a_class)
    # TODO: 在 a_class 上定义方法 m()
  end

这时,我们需要一种新的方式,它 不需要使用 class 关键字就能修改当前类,它就是 class_eval()

  • Module#class_eval() 方法(或者它的别名 module_eval())会在一个已存在类的上下文中执行一个块:
    def add_method_to(a_class)
      a_class.class_eval do
        def m; 'Hello!'; end
      end
    end
    
    add_method_to String
    "abc".m    #=> "Hello!"
    
  • 实际上 Module#class_eval() 方法和 Object#instance_eval() 方法截然不同。instance_eval() 方法仅仅会修改 self,而 class_eval() 方法会同时修改 self 和当前类。通过修改当前类,class_eval() 实际上是重新打开了该类,就像 class 关键字所做的一样。

  • Module#class_eval() 实际上比 class 关键字更加灵活,它可以对任何代表类的变量使用 class_eval() 方法,而 class 只能使用常量。另外,class 会打开一个新的作用域,这样将丧失当前绑定的可见性,而 class_eval() 方法则使用 扁平作用域,这意味着可以引用 class_eval() 块外部作用域中的变量。

当前类及其特殊情况

Ruby 解释器总是会追踪当前类:

  class MyClass
    def method_one
      def method_two; "Hello!";end
    end
  end

  obj = MyClass.new
  obj.method_one
  obj.method_two       #=> "Hello!"

在这里 method_two() 方法属于哪个类?或者说,在定义 method_two() 方法时,谁是当前对象?这种情况下,当前类不可能时 self,因为 self 根本不是类,实际上,这时当前类的角色由 self 的类来充当—-MyClass。同样的原则还可以应用于处于顶级作用域的时候。这时,当前类是 Object—-main 对象的类,这也就是为什么当在顶级作用域中定义方法的时候,这个方法会成为 Object 类实例方法的原因。

Current Class Wrap-up

  • 在类定义中,当前对象 self 就是正在定义的类。
  • Ruby 解释器总是追踪当前类(或模块)的引用,所有使用 def 定义的方法都成为当前类的实例方法。
  • 只要有一个类的引用,则可以用 class_eval()(或 module_eval())方法打开这个类。

Class Instance Variables

Ruby 解释器假定所有的实例变量都属于当前对象 self,当定义类时也是如此:

  class MyClass
    @my_var = 1
  end

在类定义的时候, self 的角色由类本身担任,因此实例变量 @my_var 属于这个类,注意 类的实例变量不同于类的对象的实例变量

  class MyClass
    @my_var = 1

    def self.read; @my_var; end
    def write; @my_var = 2; end
    def read; @my_var; end
  end

  obj = MyClass.new
  obj.write
  obj.read              #=> 2
  MyClass.read          #=> 1

上面的代码定义了两个实例变量,它们正好都叫 @my_var,但是它们分属于不同的作用域,并属于不同的对象。要牢记 类也是对象,而且需要自己在程序中追踪 self。其中一个 @my_var 变量定义于 obj 充当 self 的时刻,因此它是 obj 对象的实例变量。另外一个 @my_var 变量定义于 MyClass 充当 self 的时刻,因此它是 MyClass 的实例变量—-也就是 类实例变量

类实例变量是属于 Class 类对象的普通实例变量,类实例变量仅仅可以被类本身所访问,而不能被类的实例或子类所访问。