ruby问题,转载

类变量与类方法

执行下面的程序的话便发生了错误。请将程序进行修改以能正确运行。

class Robot def self.ping @@count += 1 end def count @@count end end r1 = Robot.new r1.ping r2 = Robot.new r2.ping r1.ping puts Robot.count

Robot类本来的形式是像下面这样:

  • 调用实例方法ping的话类的共有计数器便增加1个。
  • 调用类方法count的话便返回现在计数器的值。

在终端执行此程序的话,画面上显示3。

--

黒田努


解答与说明的显示・隐藏

解答与说明

这次完全不存在让人感到棘手的地方。实际上只要运行Ruby解释器进行修改的话,就可在比较短的时间内解决问题。

但是如果让你只用铅笔和纸来解决问题的话又怎么样呢。

我想正确率一定很低。

从寄来解答的诸君那里,也听到了“这次算是又学习了”的感慨。

***

先简单的温习一下类变量与类方法吧。

类变量在名称的前面添加 @@ 符号表示。定义了此项的类,便成为被子变量、实例所共有的变量。不能从除此之外的对象查看。

类方法是类提供的“方便的的函数(功能)”。与实例方法查看、改变类的状态相对照。类方法也可以从类的外部进行利用。

***

于是很快能发现的是,类方法与实例方法的定义是相反的。类方法名称的前面可以添加self. 。但是实例方法名称的前面不可以。

view plaincopy to clipboardprint?

  1. class Robot
  2. def ping
  3. @@count += 1
  4. end
  5. def self.count
  6. @@count
  7. end
  8. end

但是,只修改两处的的方法定义的话显然不够。试着运行的话便会出现下面的错误信息。

robot.rb:3:in `ping': uninitialized class variable @@count in Robot (NameError)

类变量的 @@count 未被定义。

应该在哪里执行类变量的初始化呢。

下面的代码是笔名为 y@su 的先生写的对 Robot 类的定义。

view plaincopy to clipboardprint?

  1. class Robot
  2. def initialize
  3. @@count ||= 0
  4. end
  5. def ping
  6. @@count += 1
  7. end
  8. def self.count
  9. @@count
  10. end
  11. end

这也能运行,但是构造函数 initialize 方法的作用是,将 Robot 实例初始化。在将类变量初始化的情况下不是那么适合。

类变量在类定义中初始化。

下面是示范解答。

view plaincopy to clipboardprint?

  1. class Robot
  2. @@count = 0
  3. def ping
  4. @@count += 1
  5. end
  6. def self.count
  7. @@count
  8. end
  9. end
  10. r1 = Robot.new
  11. r1.ping
  12. r2 = Robot.new
  13. r2.ping
  14. r1.ping
  15. puts Robot.count

大詰君、MTG君、river125君的代码,与示范解答完全一样,在此感谢。

(2009/07/30)

Ruby 练习问题集

模块与类(1)

在当前目录中存在一个下列内容的文件 mod.rb

module Mod def foo "Foo" + bar end private def bar "Bar" end end

请利用Mod 模块,定义具有类方法 foo 的类 Klass

Klass 的利用例如下。

puts Klass.foo

最后在终端输出 FooBar

--

黒田努


解答与说明的显示・隐藏

解答与说明

这是一个怎样将模块作为类方法引用的问题。

好像有一点难的样子。

MTG君在说着“很难得到正确答案”的同时,发来了下面的代码。

view plaincopy to clipboardprint?

  1. require 'mod'
  2. class Klass
  3. include Mod
  4. end
  5. puts Klass.new.foo
  6. #puts Klass.foo # => undefined method `foo' for Klass:Class (NoMethodError)

一般的使用 include 方法取出模块便是这样。

下面的代码是 river125 的答案。

view plaincopy to clipboardprint?

  1. require 'mod'
  2. class Klass
  3. include Mod
  4. def self.foo
  5. k = Klass.new
  6. k.foo
  7. end
  8. end

因为问题中写了“利用Mod 模块”,这也没错,但是要避免每次调用方法foo 都生成实例。

大詰君提供了了正确解答。

view plaincopy to clipboardprint?

  1. require 'mod'
  2. class Klass
  3. extend Mod
  4. end

Ruby参考手册的Object,就 extend 方法 “把用参数指定的模块的实例方法作为self 特殊方法追加”进行了说明。

特殊方法是什么呢。

这是在与实例方法进行对比时使用的语言。别名为单例方法。

具有某个对象 obj 的方法,被分为实例方法和特殊方法。

实例方法被其对象的类定义,并被类的实例全体所共有。

与此相对,特殊方法在对象中被定义。

更准确的说法是,对象在所属的特殊类中被定义。

总之,被一个对象专有的方法就是特殊方法。

那么,像怎样写的时候,应该怎么做呢。

view plaincopy to clipboardprint?

  1. class Klass
  2. extend Mod
  3. end

虽然方法 extend 的运行主体 self 未在表面出现,但是直接在类定义之下,所以类 Klass 就是 self

请回想一下在Ruby 语言里,类即是对象。

一句话,就是将模块 Mod 作为叫做类 Klass 的“对象”的特殊方法进行追加。

实际上,“类方法”就是类的特殊方法!

* * *

最后来看示范解答。

view plaincopy to clipboardprint?

  1. require 'mod'
  2. class Klass
  3. class << self
  4. include Mod
  5. end
  6. end

class << self ... end 是定义特殊类时的写法。在其中定义方法的话,就成为类 Klass 的类方法。

虽然比使用 extend 时的代码要长,但是好处是在希望添加 Klass 独自的类方法 baz 时可以这样写。

view plaincopy to clipboardprint?

  1. require 'mod'
  2. class Klass
  3. class << self
  4. include Mod
  5. def baz
  6. "Baz"
  7. end
  8. end
  9. end

因为明确了从模块引入的类方法与独自的类方法的关系,我喜欢下面这样的写法。

此外,还可以重写私有方法 bar

view plaincopy to clipboardprint?

  1. require 'mod'
  2. class Klass
  3. class << self
  4. include Mod
  5. private
  6. def bar
  7. "BAR"
  8. end
  9. end
  10. end

顺便说一下,下面这样大体上也能重写,但是 bar 便成了公共方法。

view plaincopy to clipboardprint?

  1. require 'mod'
  2. class Klass
  3. extend Mod
  4. private
  5. def self.bar
  6. "BAR"
  7. end
  8. end

虽然 private 是一个将以后的方法定义的作用域私有的方法,但对于特殊类的方法定义却没有效果。

(2009/08/06)