目次

9.  クラス

 

ここまで、私たちは、数々のオブジェクトを異なる種類、あるいは別の言い方を すればクラス ごとに分けて見てきました。 たとえば、文字列、整数、浮動小数点数、配列、そして後で説明する 特別なオブジェクト(true, false, そして nil)などです。 Rubyでは、これらのクラスはいつも先頭文字を大文字で書かれます。つまり、 String(文字列), Integer(整数), Float(浮動小数点数), Array(配列)... などなどです。 一般的にはある特定のクラスの新しいオブジェクトを作りたいときには、new を使います。

a = Array.new  + [12345]  #  Array  の足し算。
b = String.new + 'hello'  #  String の足し算。
c = Time.new

puts 'a = '+a.to_s
puts 'b = '+b.to_s
puts 'c = '+c.to_s
a = 12345
b = hello
c = Mon May 11 18:44:26 +0900 2009

配列や文字列は、それぞれ[...]とか'...'のように 作ることができるので、newを使って作ることはほとんどありません。 (上の例からはあまりはっきりしないかもしれませんが、String.newは 空の文字列を、Array.newは空の配列を作ります。) 数字もまた例外です。整数はInteger.newでは作ることができません。 整数が欲しければそのまま整数を書かなければなりません。

Timeクラス

それでは、Timeクラスに関してはどうでしょう? Timeオブジェクトはある瞬間の時刻を表します。 ある時刻に数を足したり引いたりして新しい時刻を作ることが出来ます。 ある時刻に1.5を加えると、1.5秒後の新しい時刻が作られます。

time  = Time.new   #  このWebページをゲットした瞬間。
time2 = time + 60  #  1分後。

puts time
puts time2
Mon May 11 18:44:26 +0900 2009
Mon May 11 18:45:26 +0900 2009

また、ある特定の瞬間の時刻をTime.mktimeを使って 作ることも出来ます。

puts Time.mktime(2000, 1, 1)          #  Y2K.
puts Time.mktime(1976, 8, 3, 10, 11)  #  私の生まれたとき。
Sat Jan 01 00:00:00 +0900 2000
Tue Aug 03 10:11:00 +0900 1976

注意:この誕生時刻は、太平洋夏時間(Pacific Daylight Time; PDT)で 表しています。Y2Kが問題になったのは、少なくとも西海岸の人にとっては、 太平洋標準時(Pacific Standard Time; PST)です。 (訳註;日本国内では時刻は日本標準時(JST)で表され、グリニッジ標準時(GMT) あるいは協定世界時(UT)より9時間進んでいます。そのため、GMT+9:00と表現されることも あります。PDTはGMTより7時間、PSTは8時間遅れています。 mktimeはコンピュータに設定されたタイムゾーンにあわせて時刻を設定するので、 実行するコンピュータによりこの結果は異なります。) カッコはmktimeの引数を一つに束ねる働きをしています。 引数を加えていくほど、時刻は正確になります。

時刻を比較メソッドを使って比較することも出来ます。 (早い時刻は遅い時刻より小さく なります。) そして、ある時刻を別の時刻から引き算をするとその2つの時刻の間の 秒数を得ることが出来ます。これらを使っていろいろ遊んでみましょう。

練習問題

• 10億(One billion)秒... (もし記録が残っているなら)あなたの生まれた 正確な時刻を見つけ、いつ10億秒歳になる(あるいはなった)のかを 計算してみなさい。そしてカレンダーに印をつけましょう。

• ハッピーバースデー!  生まれた年、月、そして日を順に訊いて そこから年齢を計算します。そして、過ごしてきた誕生日それぞれに対して 大きなおめでとう!をプレゼントしましょう。

Hashクラス

もう1つ、便利なクラスHashがあります。 ハッシュは配列に非常に似ていて、いろいろな種類のオブジェクトを 指し示すことの出来る項目のようなものをまとめています。ただし、 配列ではそれらの項目は1列に並んでおり、それらの1つ1つには (0から)数字が振られているのに対して、ハッシュではその項目が 並んではおらず(ある意味ごちゃ混ぜになっていて)、それぞれの 項目を参照するのに、数ではなくどんな オブジェクトも 使うことが出来ます。まとめて処理したいひとまとまりのデータがあって、 それが序列をつけたリストとしてはそぐわない時などが、ハッシュ の出番です。例えば、このチュートリアルを作るときのコードの異なる部分 に対して使う色などです。

colorArray = []  #  Array.new と同じ
colorHash  = {}  #  Hash.new と同じ

colorArray[0]         = ''
colorArray[1]         = ''
colorArray[2]         = ''
colorHash['strings']  = ''
colorHash['numbers']  = ''
colorHash['keywords'] = ''

colorArray.each do |color|
  puts color
end
colorHash.each do |codeType, color|
  puts codeType + ':  ' + color
end
赤
緑
青
strings:  赤
keywords:  青
numbers:  緑

配列を使った場合は、0の項目が文字列を表し、 1が数字を...というように覚えていなければなりません。 でも、もしハッシュを使えば簡単になります。項目の名前を'string'にして、 もちろんここに文字列の色を入れるのです。面倒な対応などは覚える必要がありません。 ここで注意しておいたほうが良いのは、eachを使った時、 ハッシュに入っているオブジェクトがハッシュに入れた時と同じ順序で 取り出されるとは限らないことです。 (少なくとも、私がこれを書いたときは違いました。次は順番どおり出てくるかもしれません。 どちらにしても、ハッシュの順序に関してはわからないのです。) 配列はものの順序を保持しますが、ハッシュはしません。

ハッシュの項目の名前としては文字列が使われることが普通ですが、やろうと思えば どんな種類のオブジェクトでも使うことができます。(それが必要になるとは あまり思えませんが)、配列や他のハッシュだってOKです。

weirdHash = Hash.new

weirdHash[12] = 'モンキーズ'
weirdHash[[]] = 'からっぽ'
weirdHash[Time.new] = 'するなら今だ'

ハッシュと配列はそれぞれ適材適所があります。ある特定の問題に 対し、どちらが最適かを決めるのはあなたの責任です。

クラスの拡張

前の章の最後で、与えられた整数を英語のスペリングに変換するメソッドを 書きました。しかし、それは整数が持つメソッドではなく、単に 一般的な「プログラム」が持っているメソッドでした。たとえば englishNumber 22と書く代わりに、22.to_engのように 書くことができたらナイスだと思いませんか? こうすればそれができます。

class Integer

  def to_eng
    if self == 5
      english = 'five'
    else
      english = 'fifty-eight'
    end

    english
  end

end

#  2つの数字についてチェックしましょう。
puts 5.to_eng
puts 58.to_eng
five
fifty-eight

はい、テスト完了。動いているようです。 :)

さて、これで整数メソッドを定義したことになります。 Integerクラスに飛び込み、そこでメソッドを定義し、 そしてそこから飛び出してきました。 その結果、すべての整数はこの(ある意味不完全な)メソッドを持つように なります。実際、もしto_sのようなビルトインメソッド (最初から定義されているメソッド)の振る舞いが気に入らないとしたら、 このやり方を使って再定義ができてしまいます...しかしお勧めはしませんが。 何か新しいことをしたくなったときは、古いメソッドをそのままにして 新しい物を作るほうが良いようです。

さて、まだ混乱していますか? このプログラムをもう少し見ていきましょう。 ここまで、どんなコードを実行するにしてもメソッドを作るにしても、それは デフォルトの「プログラム」オブジェクトの中で行ってきました。 でも、今のプログラムでは、最初にそのオブジェクトから離れて、 Integerというクラスの中に入っています。 そして、そこでメソッドを定義し(これによって整数メソッドができます。)、 すべての整数がそれを使うことができるようになりました。 メソッドの内側では、そのメソッドを使っているオブジェクト(つまり整数)を 参照するのにselfを使います。

クラスを作る

今までにたくさんのオブジェクトの種類(クラス)を見てきました。 しかし、Rubyが持っていないオブジェクトの種類が欲しくなることは良く あります。 ラッキーなことに、新しいクラスを作るのは古いクラスを拡張するのと 同じくらい簡単です。たとえば、Rubyの中にさいころが 欲しくなったとしましょう。これがDieクラスの作り方です。

class Die

  def roll
    1 + rand(6)
  end

end

#  2つのさいころを作ってみましょう。(訳註:die(さいころ)の複数がdice)
dice = [Die.new, Die.new]

#  そして、転がします。
dice.each do |die|
  puts die.roll
end
4
2

(もし、乱数の節を飛ばしていたとしたら、rand(6)というのは、 0から5までの乱数を与えるというふうに理解してください。)

はい、私たちのオブジェクトが出来ました。 さいころを(リロードボタンを使って)何度か転がしてみて、何が出るか みてみましょう。

これで、オブジェクトに対してどんな種類のメソッドも定義できるようになりました...。 しかし何かまだ足りないようです。 こんなふうにオブジェクトを作ったりしていると、何か変数を覚える前にしていた プログラミングのような感覚になりませんか? たとえば、今作ったさいころのプログラムを見てみましょう。 さいころを振ることはできます。そして、毎回違った数字が現れています。 でも、その数字をとどめておきたいとしたら、その数字を指し示す変数が欲しくなります。 まともなさいころなら、数をひとつ保持 することができて、 さいころを振ったときに、その数が変化するようにすべきだと思います。 もし、さいころ自体を変数に入れているなら、出ている目を別の変数にいれる というのは良くないでしょう。

でも、もし出た目をrollの中で(局所)変数に保存しようとすると、 rollが終わるやいなや、その値はどこかに行ってしまいます。 この数は別の種類の変数に保存する必要があるようです。

インスタンス変数

普通、ある文字列について話をしたい時には、それを文字列 と呼びます。 でも、文字列オブジェクト と呼ぶこともできます。 時々、プログラマはこれをString(文字列)クラスの インスタンス と呼んだりします。でもこれは、文字列 の やや気取った(そして、ちょっと長たらしい)言い方に過ぎません。 要するに、クラスのインスタンス とは、クラスのオブジェクトのことです。

さてそれで、インスタンス変数とはオブジェクトの変数のことです。 メソッドの局所変数はメソッドが終わるまでの命です。 一方、オブジェクトのインスタンス変数は、オブジェクトが生き残っている限り 生き残ります。インスタンス変数を普通の局所変数と区別するために、 その名前の先頭には@をつけます。

class Die

  def roll
    @numberShowing = 1 + rand(6)
  end

  def showing
    @numberShowing
  end

end

die = Die.new
die.roll
puts die.showing
puts die.showing
die.roll
puts die.showing
puts die.showing
1
1
5
5

いいですねぇ。これで、rollはさいころを振ってshowingは 何の目が出ているかを知らせるようになりました。 でも、もし、さいころを振る前(つまり、@numberShowingの変数を セットする前)に何の目が出るかを見ようとしたら、いったいどうなるでしょう。

class Die

  def roll
    @numberShowing = 1 + rand(6)
  end

  def showing
    @numberShowing
  end

end

#  このさいころをもう一度使うつもりはないので、
#  変数に保存しておく必要はありません。
puts Die.new.showing
nil

ふむふむ。少なくともエラーにはならないようです。でもこの、 nilという出力の意味を推し量ったとしても、 「振られていない」さいころに出た目というのにはやはり違和感があります。 これを避けるには、さいころオブジェクトが作られた瞬間に出る目を 決めてしまえば良いわけです。これをするのがinitializeです。

class Die

  def initialize
    #  ここでは、さいころを振るだけにします。
    #  最初から6にセットしておくとか、
    #  他にやりたい放題できることは確かですけど。
    roll
  end

  def roll
    @numberShowing = 1 + rand(6)
  end

  def showing
    @numberShowing
  end

end

puts Die.new.showing
4

オブジェクトが生成されるときには、(それが定義されてれば) 必ずinitializeメソッドが実行されます。

私たちのさいころはほとんど完璧です。あと、出る目をセットする方法が あったらいいなと思いませんか? さぁ、このcheatメソッドを 書いてみましょう。プログラムを書いたら(そして、もちろん書いた メソッドがきちんと動くかテストしたら)この文章に戻ってきましょう。 7の目をセットできてしまうなんてことがないように 確認してくださいね。

ちょっとトリッキーでしたが、これでかなりクールなことができるように なりましたね。 さて、それでは、また別のもっと楽しい例をあげてみましょう。 たとえば、簡単な仮想ペットを作ってみるのはどうでしょう。 赤ちゃんドラゴンです。 たいがいの赤ちゃんと同じく、そのドラゴンは、食べ、寝、うんちをします。 ということは、食べさせたり、寝かしつけたり、散歩させたり、もできるように しないといけません。内部では、ドラゴンはおなかがすいているのか、 疲れているのか、出かけたいのかなどを記憶している必要があるのですが、 赤ちゃんドラゴンにそれを聞いてみてもそれはわかりません。 人間の赤ちゃんに「おなかすいた?」と訊いてもしかたがないのと同じです。 その代わり、赤ちゃんドラゴンと会話するための別の方法を2,3提供しましょう。 そして、彼が生まれる時には名前を付けられるようにします。 (newメソッドに何を渡しても、それはinitializeメソッドに引き継がれます。) OK, では、はじめます。

class Dragon

  def initialize name
    @name = name
    @asleep = false
    @stuffInBelly     = 10  #  最初彼はおなかいっぱいです。
    @stuffInIntestine =  0  #  トイレはまだ大丈夫。(Bellyは胃、Intestineは腸です。)

    puts @name + ' は生まれました.'
  end

  def feed
    puts 'あなたは ' + @name + 'に食べ物を与えます.'
    @stuffInBelly = 10
    passageOfTime
  end

  def walk
    puts 'あなたは ' + @name + 'をトイレに連れて行きます.'
    @stuffInIntestine = 0
    passageOfTime
  end

  def putToBed
    puts 'あなたは ' + @name + ' を寝かしつけます.'
    @asleep = true
    3.times do
      if @asleep
        passageOfTime
      end
      if @asleep
        puts @name + ' はいびきをかいて, 部屋中煙だらけ.'
      end
    end
    if @asleep
      @asleep = false
      puts @name + ' はゆっくり目を覚ます.'
    end
  end

  def toss
    puts 'あなたは ' + @name + ' を空中に投げ上げます.'
    puts '彼はキャハキャハ笑い, あなたの眉毛は焼け焦げた.'
    passageOfTime
  end

  def rock
    puts 'あなたは ' + @name + ' を優しく揺すります.'
    @asleep = true
    puts '彼は短くうとうと...'
    passageOfTime
    if @asleep
      @asleep = false
      puts '...でもやめるとすぐ起きちゃう.'
    end
  end

  private

  #  "private"というのは、ここで定義されているメソッドが
  #  オブジェクト内部のものであるという意味です。
  #  (あなたはドラゴンにえさを与えることはできますが、
  #  おなかがすいているかどうかを訊くことはできません。)

  def hungry?
    #  メソッドの名前は"?"で終わることができます。
    #  通常、メソッドがtrueかfalseのどちらかを返すときだけ、
    #  この名前を使います。このように:
    @stuffInBelly <= 2
  end

  def poopy?
    #うんちが出そう?
    @stuffInIntestine >= 8
  end

  def passageOfTime
    if @stuffInBelly > 0
      #  食べたものは、胃から腸へ移動
      @stuffInBelly     = @stuffInBelly     - 1
      @stuffInIntestine = @stuffInIntestine + 1
    else  #  私たちのドラゴンは飢えました!
      if @asleep
        @asleep = false
        puts '彼は突然跳び起きます!'
      end
      puts @name + ' は飢え死にしそう!  死に物狂いの彼は"あなた"を食べてしまいました!'
      exit  #  プログラムを終了します。
    end

    if @stuffInIntestine >= 10
      @stuffInIntestine = 0
      puts 'おっと!  ' + @name + ' はやっちゃったようです...'
    end

    if hungry?
      if @asleep
        @asleep = false
        puts '彼は突然起きます!'
      end
      puts @name + 'のおなかはゴロゴロ言ってます...'
    end

    if poopy?
      if @asleep
        @asleep = false
        puts '彼は突然起きます!'
      end
      puts @name + ' はうんちをしたくて踊っています...'
    end
  end

end

pet = Dragon.new 'Norbert'
pet.feed
pet.toss
pet.walk
pet.putToBed
pet.rock
pet.putToBed
pet.putToBed
pet.putToBed
pet.putToBed
Norbert は生まれました.
あなたは Norbertに食べ物を与えます.
あなたは Norbert を空中に投げ上げます.
彼はキャハキャハ笑い, あなたの眉毛は焼け焦げた.
あなたは Norbertをトイレに連れて行きます.
あなたは Norbert を寝かしつけます.
Norbert はいびきをかいて, 部屋中煙だらけ.
Norbert はいびきをかいて, 部屋中煙だらけ.
Norbert はいびきをかいて, 部屋中煙だらけ.
Norbert はゆっくり目を覚ます.
あなたは Norbert を優しく揺すります.
彼は短くうとうと...
...でもやめるとすぐ起きちゃう.
あなたは Norbert を寝かしつけます.
彼は突然起きます!
Norbertのおなかはゴロゴロ言ってます...
あなたは Norbert を寝かしつけます.
彼は突然起きます!
Norbertのおなかはゴロゴロ言ってます...
あなたは Norbert を寝かしつけます.
彼は突然起きます!
Norbertのおなかはゴロゴロ言ってます...
Norbert はうんちをしたくて踊っています...
あなたは Norbert を寝かしつけます.
彼は突然跳び起きます!
Norbert は飢え死にしそう!  死に物狂いの彼は"あなた"を食べてしまいました!

ふー! もちろん、会話的にプログラムが動けばなお良いのですが、 その部分は後であなたが作ることができるでしょう。 ここでは、新しいドラゴンクラスを作るのに直接関係する部分を示してみました。

この例では、2つの新しいものが出てきています。 1つ目はexitです。 これは非常に単純で、プログラムをその場で終了させるメソッドです。 2つ目はprivateという言葉です。 これはクラス定義の真ん中に挿入されています。 プログラムはこれがなくても動くのですが、 ドラゴンに対してある特定のメソッドだけを行うことができて、 それ以外はドラゴン内部で起こるようにする、 という制限をつけたい時に必要です。 これは、「フードの下に覆い隠す」というふうに思えば分かるのではないかと思います。 別の例を挙げましょう。運転する時、あなたが知っていなければならない操作は、 アクセルペダルとブレーキペダルの踏み方、ハンドルの回し方などです (自動車のメカニックでない限り)。 プログラマはこれを車に対するパブリック インターフェイス と呼びます。 エアバッグがいつ開くかというのは車の内部の問題です。 普通のユーザ(ドライバー)はこのことを知る必要はありません。

これを、もう少し具体的にプログラムの行と関連させるため、 (たまたま私の仕事と関係あるのですが)、ビデオゲームの中の車をどんなふうに 作るかについて考えてみましょう。 最初に、パブリックインターフェイスをどうするかを決めたくなると思います。 別の言い方をすれば、あなたの車オブジェクトに対して、人々がどのメソッドを 呼ぶことが出来るようにするかということです。 おそらく、アクセルペダルとブレーキペダルを踏めるようにするのは 必要だし、どのくらい強くペダルを踏んでいるのかを特定するのも 必要でしょう。(べた踏みと軽くたたくのでは大きな違いです。) それから、ハンドルを切るのも必要でしょうし、どのくらい回すのかを 言う必要だってあるでしょう。それと、クラッチをつけたくなったり、 ウインカーをつけたくなったり、ロケットランチャー、アフターバーナー、 キャパシタ、などなど・・・。要はどんなゲームを作っているかによります。

で、車オブジェクトの内部ではずっとたくさんのことが行われている必要が ありますが、それだけではなくて、(最も基本的なものとして) 車のスピード、方向、位置などが必要です。 こういった属性はアクセルペダルやブレーキペダルを踏むことと、 ハンドルを回すことで変化するでしょうが、もちろん、ユーザーが直接 位置を指定できたりしてはまずいです(まるで、ワープのようになって しまいますから)。あるいは、スリップ、ダメージ、空気抵抗など を記録したくなるかもしれません。これら全ては車オブジェクトの内部 のものです。

練習問題

OrangeTreeクラスを作ってみなさい。 このクラスには、木の高さを返すheightメソッドと、 メソッドを呼ぶことで、1年分時間を進めるoneYearPassesメソッドがあります。 毎年、この木は成長し大きくなります(オレンジの木が1年に伸びる分だけ)。 そして、ある年限が来たら(これもメソッド呼び出しによります) その木は死んでしまいます。最初の2,3年は実をつけませんが、その後は 実がなる様にします。で、成長した木は若い木よりたくさん実をつけます。 このあたりはあなたが納得するよう調節してみましょう。 そして、もちろんcountTheOranges(木になっているオレンジの数を返す)メソッドと、 pichAnOrange(オレンジをひとつ摘むメソッド。このメソッドで @orangeCountが、1だけ小さくなり、いかにおいしいオレンジだったかを告げる文字列か あるいは、もう木にオレンジがなっていないことを告げる文字列かを返します。)を 実行できるようにしなければいけません。 それと、ある年に取り残したオレンジは次の年には落ちてしまうようにしましょう。

• 赤ちゃんドラゴンと会話が出来るようなプログラムを書いてみましょう。 feedwalkのようなコマンドを入力できるようにして ドラゴンに対してそれらのメソッドが呼ばれるようにします。 もちろん、入力されたものは文字列なので、ある種のメソッドに転送する処理 をしなくてはならないでしょう。つまり、何の文字が入力されたかをチェックして適切な メソッドを呼び出すという処理です。

これでプログラムに関してはすべて完了です!! でもちょっと待ってください。 まだ、Eメールを送ったり、ファイルをコンピュータにしまったり読み出したり、 あるいは、ウインドウズやボタンを作ったり、はたまた3次元の世界を作ったり、 のようないろいろなことをするクラスについてまだ何も説明をしていませんでしたね。 はい、ここですべてを示すことが不可能な程たくさんの 使用可能な クラスがあります。そのほとんどは私でさえ知りません。 私が言えるのは その大部分がどこで見つけることが出来るかということです。 そうすればプログラムで使いたいクラスについて、ひとつずつ学ぶことが出来るはずです。 でも、あなたを送り出す前に、後ひとつの章を授けたいと思います。 それは、あなたが知っておくべきRubyのもうひとつの特徴で、ほかのほとんどの言語には 無いのだけれど、それ無しでは私は生きていけないくらいのものです。 それは、ブロックと手続きオブジェクトです。

 

目次