6. 类、模块

6.1 重定义类时,是否会覆盖原来的定义?

重定义同一个类时,意味着对原有定义进行补充,不会覆盖原来的定义。而重定义方法时,则会覆盖原有定义。

6.2 有类变量吗?

从1.6版本开始出现类变量。类变量名前都带有`@@'。

class Foo
  @@F = 0
  def foo
    @@F += 1
    print @@F, "\n"
  end
end

在1.4以前的版本中,使用容器类(Array、Hash等)来代替类变量。

class Foo
  F = [0]
  def foo
    F[0] += 1
    print F[0], "\n"
  end
end

6.3 什么是类的实例变量?

class Foo
  @a = 123 # (1)
  def foo
    p @a  # (2) ... 123でなくnilになる。
  end
end

(1)是类的实例变量、(2)是通常的实例变量。(2)属于Foo类的实例,而(1)属于类对象Foo(Class的实例)。

不能在实例方法中直接访问类的实例变量。

因此上面的实例变量尚未初始化,其值为nil

6.4 什么是特殊方法?

特殊方法是指某实例所特有的方法。

通常这么使用。

foo = Foo.new
def foo.hello
  print "Hello\n"
end
foo.hello

若想向类中添加方法,却又不想生成子类时,可以使用特殊方法。

Java程序员可能会觉得它很像inner class。

6.5 什么是类方法?

类方法就是指类的特殊方法。上面我们提到,特殊方法是归属于某对象的方法,那么这里所说的“类的特殊方法”将作何解释呢?其实Ruby中有一个“元类”的概念。它是Class类的实例,同时又被所有的类所共有。类方法就定义在这个元类中。

从形式上看,将类名当作被调的方法就是类方法。

下面,我们就在Class类的Foo实例中添加一个特殊方法。

class Foo
  def Foo.test
    print "this is foo\n"
  end
end

像这样来调用它。

Foo.test

您有没有注意到什么?

对了,这就是所谓的类方法。

当然了,Class中所定义的方法也可以用作类方法。

6.6 什么是特殊类?

上面我们提到了特殊方法

简单说来,在Ruby中可以向对象(实例)中添加方法。

进一步讲,如果您想像操作类那样来操作对象时,该怎么办呢?

可千万别说您没这么想过(^^;

使用特殊类的话,您就可以做到这一点。

class Foo
  def hello
    print "hello.\n"
  end
end

foo = Foo.new
foo.hello

#=> hello.

class << foo
  attr :name, true
  def hello
    print "hello. I'm ", @name, ".\n"
  end
end

foo.name = "Tom"
foo.hello

#=> hello. I'm Tom.

多棒呀。

好了,现在要问问题了

问题 如果不使用private_class_method的话,能不能将类方法变成private呢?

解答: 类方法就是类的特殊方法。

所以我们可以耍个小花招,这么处理它。

class Foo
  # ...
end

class << Foo
  def class_method
    print "class method\n"
  end
  private :class_method
end
Foo.class_method #=> Error

定义特殊方法时,既可以像上面这样在特殊类中加以定义,也可以直接使用def obj.method来完成定义。

另外,您还可以在模块中定义特殊方法(而且是private方法)。

6.7 什么是模块函数?

定义模块函数时,它们既是模块的特殊方法,同时又是private类型。例如,您既可以这样

Math.sqrt(2)

来调用它,又可以这样

include Math
sqrt(2)

使用include来调用它,的确十分方便。

若您想把某方法定义成模块函数时,可以在模块定义中添加如下代码

module_function :method_name

即可完成定义。

6.8 类和模块有什么区别?

模块不能生成实例,而类不能被include。

6.9 模块可以生成子类吗?

在类(模块)中include模块之后,就可实现类似于多重继承的Mix-in功能。它与父类子类之间的这种直接继承关系有所不同,内含模块的类与该模块之间存在is_a?的关系。

6.10 在类定义中定义类方法 和 在顶层中定义类方法 之间有什么不同?

您在前者中可以直接使用常数,而在后者中则必须指定类名才行。

6.11 loadrequire有什么不同?

load只能加载用Ruby编写的源文件(*.rb)。

require还可以加载*.o文件。另外,一旦require某文件之后,即使再次require也不会进行加载。

加载路径也有所不同。

6.12 includeextend有什么不同?

include负责将module插入到类(模块)中,这样就能以函数的形式来调用方法;而extend负责将module插入到对象(实例)中,这样就添加了特殊方法。

6.13 self是什么?

self是指执行方法的主体本身。在函数形式的方法中,是把self当作被调的。

6.14 MatchData中的beginend分别返回什么?

它们作用于$~,分别返回$0$1等原来字符串的开始位置和终止位置。请参考展开标签中的例子。

6.15 如何使用类名来获得类?

如果我有classname = "SomeClass"时,如何生成SomeClass类的实例呢?主要有两个解决方法。

  1. eval(classname).new
  2. Object.const_get(classname).new

第1种方法既简单又可以处理嵌套类(Net::HTTP等),但如果在CGI环境中滥用它的话,将十分危险。

而第2种方法却又无法处理嵌套类的问题。但如果进行以下处理的话,就可以处理嵌套类了。

# 如果只考虑Ruby 1.8之后的版本的话,可以这样
c = classname.split(/::/).inject(Object) {|c,name| c.const_get(name) }
c.new

# 若想兼顾Ruby 1.6的话,则需要
c = Object
classname.split(/::/).each do |name|
  c = c.const_get(name)
end
c.new