Rubyのデータはすべてオブジェクトで,オブジェクトはデータとそのデータに対する処理機能としてのメソッドからなります. オブジェクトには種類があります. これまでにも「数値」,「文字列」,「配列」などのオブジェクトを扱ってきました. これらのオブジェクトにはそれぞれに固有のデータがあり,またメソッドがあります. Rubyでは最初から用意されている種類のオブジェクトを使えるだけでなく,プログラムの中で新しい種類のオブジェクトを作ることもできます.
さて,オブジェクトのもつデータとメソッドの仕様はどのように決まるのでしょうか. 新しい種類のオブジェクトはどのように作ることができるのでしょうか. Rubyでは,オブジェクトの仕様を「クラス」によって記述します. ある程度規模の大きなRubyプログラムは,クラスをベースにして記述していくのが一般的です. オブジェクトを(クラスによって)設計しながらプログラムを作っていくスタイルをオブジェクト指向プログラミングといいます.
ここでは,クラスとオブジェクト指向プログラミングについて,その基礎を簡単に説明します.
もくじ
例題
プログラム
例題のプログラムを以下に示します. プログラムを分かりやすくするために, 次の二つのファイルに分割して作成してあります(分割しなければ実現できないわけではありません).
nsguess.rb | 処理本体 |
---|---|
nsg.rb | クラス定義 |
nsguess.rbが実際に処理を行う部分です. nsg.rbは,nsguess.rbの処理で利用する定義を行っています. nsguess.rbでnsg.rbをrequireにより取り込んでいます.
プログラムは次のように実施します(nsguess.rbとnsg.rbが同じディレクトリにあるものと仮定します). なお正解を当てられない場合でも[Ctrl]+[d]を入力すると正解を示して終了します.
$ ruby nsguess.rb 3 ------------------------------------------------------------------------ 3桁の秘密の数字列を当ててください ------------------------------------------------------------------------ (!) 予想した数字列に対して次の情報を表示します 一致: 秘密の数字列と位置も含めて一致した数字の個数 関連: 「一致」以外で秘密の数字列に含まれる数字の個数 無関係: 秘密の数字列には含まれない数字の個数 ------------------------------------------------------------------------ 予想(#1)? 123 123 一致:0,関連:0,無関係:3 予想(#2)? 456 456 一致:0,関連:0,無関係:3 予想(#3)? 879 879 一致:1,関連:2,無関係:0 予想(#4)? 897 897 一致:0,関連:3,無関係:0 予想(#5)? 978 978 一致:0,関連:3,無関係:0 予想(#6)? 789 正解!!(予想回数:6) $ ruby nsguess.rb ------------------------------------------------------------------------ 4桁の秘密の数字列を当ててください ------------------------------------------------------------------------ (!) 予想した数字列に対して次の情報を表示します 一致: 秘密の数字列と位置も含めて一致した数字の個数 関連: 「一致」以外で秘密の数字列に含まれる数字の個数 無関係: 秘密の数字列には含まれない数字の個数 ------------------------------------------------------------------------ 予想(#1)? 1234 1234 一致:0,関連:1,無関係:3 予想(#2)? ギブアップ! # [Ctrl]+[d]を入力する 正解は8726でした
プログラム(処理本体)
処理本体の部分です. 先頭でnsg.rbをrequireにより取り込んでいます.
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]
プログラム(定義部)
nsguess.rbでの処理で利用するさまざまな定義を行っています.
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]
クラス
Rubyのオブジェクトは全て何らかのクラス(class)に属します. 整数オブジェクトは整数クラス,文字列オブジェクトは文字列クラス, 配列オブジェクトは配列クラスに属するオブジェクトです. たとえば「1」と「2」はともに整数クラス(正確には固定長整数クラス)のオブジェクトです. 一方,"This is a string",[0,1,2]はそれぞれ文字列,配列という異なるクラスのオブジェクトです.
Rubyではオブジェクトの属するクラスをclassというメソッドで調べることができます.
"This is a string".class # ==> String [0,1,2].class # ==> Array
この例の場合,"This is a string"というオブジェクトのクラスはString,[0,1,2]というオブジェクトのクラスはArrayということが分かったわけです.
クラスとはオブジェクトの種類を規定するものであるといえます. 同じクラスのオブジェクトは,同じ種類のデータと同じメソッドをもっています. 言い換えれば,クラスとは, そのクラスに属するオブジェクトがどのようなデータと機能を備えているか決める仕組み,オブジェクトの仕様書,設計書であるといえます.
一般にオブジェクト指向の世界では,クラスに属するオブジェクトのことを,そのクラスのインスタンス(instance)とよびます. クラスがオブジェクトの種類を規定していて,そのクラスに属している具体的なオブジェクトの実例をインスタンスとよぶわけです. 各インスタンス(オブジェクト)は,それぞれが別々の実体をもちます. インスタンスは,プログラム実行中にクラスの仕様にしたがって作りだされます. Rubyでクラスのインスタンスを作るには,一般的にはそのクラスのnewメソッドを利用します.
a = Array.new(5) # 長さ5の配列(Arrayクラスのインスタンス)を生成する s = String.new("abc") # "abc"という文字列(Stringクラスのインスタンス)を生成する
明示的にnewを使わなくてもインスタンスを生成できる場合もあります. これまでにも見てきた通り,数値,文字列,配列などは,データそのものを記述することで新しくインスタンスを生成できます.
n = 8 # 整数のインスタンスを新たに生成する s = "string" # 文字列のインスタンスを新たに生成する a = [0,1,2] # 配列のインスタンスを新たに生成する
なお,オブジェクトのもつデータのことを属性(attribute),内部状態などと呼びます. 属性値(属性の値)はオブジェクト(インスタンス)のインスタンス変数(instance variable)で管理されます(インスタンス変数の値が属性値というわけです).
クラスの階層構造
さきほども見たように,Rubyではclassメソッドで,オブジェクトのクラスを調べることができます.
1.class # ==> Fixnum 10000000000000.class # ==> Bignum
整数「1」と「10000000000000」のクラスが「Integer」ではなく「Fixnum」と「Bignum」になっています. これらはいずれも「Integer」クラスのサブクラス(subclass)です. 逆に「Integer」クラスは「Fixnum」と「Bignum」のスーパークラス(superclass)とよばれます. サブクラスはスーパークラスを細分類したクラスになります.
Integer ─┬─ Fixnum │ └─ Bignum
「1」と「10000000000000」は異なるクラスのインスタンスですが,いずれも「Integer」の一種です. is_a?メソッドを使えば,オブジェクトがあるクラスに属するインスタンスの一種とみなせるかどうかを判定することができます.
1.is_a?(Integer) # ==> true 10000000000000.is_a?(Integer) # ==> true 1.0.is_a?(Integer) # ==> false
数値には整数の他に浮動小数点数もあります. Rubyの数値関連のクラスは全体で次のような関係になっています.
Numeric─┬─Integer ─┬─ Fixnum │ │ │ └─ Bignum └─ Float
この例のように,クラスには階層関係があります. たとえば,「生物」が「動物」,「植物」などに大別され,さらに「動物」は「無脊椎動物」と「脊椎動物」に分けられるなどのように大きな分類がさらに細かく分類されるようなものです.
ところで「Integer」クラスが「Fixnum」と「Bignum」に分類されるのは, Ruby(コンピュータ上で)で小さな整数と大きな整数のデータ表現が異なるためです. 実際には,整数を使ったプログラムを作成する上でこれらを区別しなければならないことはまずありません.
Rubyに組み込まれているクラス
Rubyには数値(整数,浮動小数点数),文字列,配列の他にも,ファイル,手続き,スレッド,正規表現など多数のクラスが最初から組み込まれています. 組み込みクラスについては,Rubyのドキュメントを参照して下さい.
オブジェクト指向プログラミング
オブジェクト指向プログラミングとは,オブジェクトを設計して,オブジェクト間のデータのやりとりによってプログラムを構成する手法です. オブジェクト指向プログラミングの特徴とされることに,クラス,クラスの 継承,あるいはポリモルフィズム(polymorphism;多相性)といった概念があげられます.
オブジェクト指向プログラミングのもっとも重要な特徴はカプセル化(encapsulation)です. カプセル化とは,プログラム内で論理的にデータを構造化した上でそれらを操作する機能(メソッド)と一体化してオブジェクトとしてまとめ,オブジェクトのデータをオブジェクトの外部から直接操作できないように閉じ込めることを意味します. オブジェクトのデータは決められたメソッドを通してのみ操作され,外部での処理で予期しない形で変更されてしまうことがなくなります. カプセル化とは,オブジェクトをブラックボックス化することだといえます.
カプセル化することで,オブジェクトを抽象的なプログラムの部品として使えるようになります. オブジェクトがメソッドを通してどのように振舞うかが分かっていれば,オブジェクトの実際のデータ構造がどうなっているか,その詳細な実装方法に左右されることなく,オブジェクトを利用することができます. カプセル化しておくことで,プログラムを作成する過程でオブジェクトのデータ構造を変更する必要があったとしても,メソッドの振舞いさえ変えなければ,オブジェクトの外部のプログラムは全く変更する必要がなくなります.
またこのときプログラムの変更部分がオブジェクトの定義(クラス)内部に限定されることも重要です. カプセル化されていないとすれば,プログラムの規模が大きくなるほど,データがプログラムのどこでいつ変更されるか把握することが困難になります. カプセル化することでプログラムを管理することが容易になります.
クラスの作成
クラスを作成するとは,新たなオブジェクトを設計することです. オブジェクトを設計するとは,つまり,どのような属性データをもち,それを操作するどのようなメソッドを持つかを定義することです.
クラスはゼロから設計することもできますし,既存のクラスをベースにして設計することもできます. 既存のクラスをベースとする場合には,新たに作成するクラスは,既存のクラスの属性データやメソッドを基本的に継承(受け継ぐ)することになります. この継承の元となるクラスがスーパークラス,そこから新たに作成したクラスがサブクラスとなります.
クラスの仕様はクラス定義式で記述します.
class クラス名 # クラス名は定数として扱われる # このクラスの定義を記述する end
例題のプログラムでも次のようなクラスを定義しています.
011 # 「数字列予想ゲーム」のクラス
012 class NumberSequenceGuess
:
067 end # class NumberSequenceGuess
initializeメソッド,インスタンス変数
クラスのnewメソッドを使ってインスタンスを新たに生成する際に必要となる処理はinitializeという特別なメソッドに定義します. newメソッドが呼び出されるとインスタンスが生成されます. またそのときnewに渡された引数がそのままinitializeメソッドに渡されて処理が行われます.
initializeメソッドで行う主な処理として,インスタンスの属性を扱うインスタンス変数を用意することが挙げられます. インスタンス変数の変数名には必ず「@」を先頭に付けます.
014 # 初期化処理
015 def initialize(n=4) # n:桁数(defaultは4)
016 @n = n # 桁数
017 @seq = generate_sequence(n) # 数字列の生成
018 @gmatch = GuessMatch.new(n) # 予想チェック用データ
019 end
なお初期化処理が要らないのであれば,initializeメソッドを書く必要はありません.
インスタンスメソッド
オブジェクトの機能として利用するメソッドを実装するにはクラスの定義の中でメソッドを定義します. 特別な定義をしない限り,クラス定義の中で定義したメソッドはインスタンスのメソッドとなります. インスタンスメソッドでは,引数,ローカル変数の他に,インスタンス変数(@から始まる名前をもつ),クラス定数,クラス変数(@@から始まる名前をもつ)などを参照することができます.
たとえば,例題のプログラムでは,次のような定義をしています.
011 # 「数字列予想ゲーム」のクラス 012 class NumberSequenceGuess : 021 # 予想チェックの結果を得る 022 def info(guess) 023 exact,rel = matched_info() 024 [exact,rel,@n-exact-rel] 025 end :
これにより,NumberSequenceGuessクラスのインスタンスにinfoというメソッドが定義されることになります. nsguess.rbでこれを利用しています.
: 015 require 'nsg' # nsg.rbの内容を読み込む : 054 secret = NumberSequenceGuess.new(n) # 「数字列予想ゲーム」インスタンス : 090 exact,rel,other = secret.info(guess) :
プライベートメソッド
インスタンスメソッドを定義するときにその下請け作業をするようなメソッドを使う場合があります. もちろん,そのような下請けメソッドもクラス内で定義すれば,必要な機能を実現することができます.
ただ,そのような下請けメソッドは単独でオブジェクトの機能として使うものではないでしょうから,オブジェクトのメソッドとして使えるようになっている必要はありません. むしろそのような内部の処理が不必要に表にでてしまうことは好ましくないといえます. オブジェクトのメソッドを設計する際には,オブジェクトの機能として使うメソッドと内部で下請けを行うメソッドは区別して,実際にオブジェクトの機能となるメソッドのみがインスタンスのメソッドとして使えるようしておくべきです.
クラス定義の中で「private」と書くと,その後に定義するメソッドは,すべてプライベートメソッド(private method)になります. プライベートメソッドが利用できるのはそのクラスの内部のみになり,外部には非公開となります. プライベートメソッドはオブジェクトのメソッドとしては使えません. 一方,プライベートでないメソッドをパブリックメソッド(public method)といいます.
011 # 「数字列予想ゲーム」のクラス
012 class NumberSequenceGuess
:
052 private ## 以下はプライベートメソッド
053
054 # 長さnの数字列(0-9)をランダムに生成する
055 # すべて異なる数字とする
056 def generate_sequence(n)
057 return nil if n > 10
058 pool = Array.new(10) { |i| i } # [0,1,...,9]
059 n2 = n**2
060 n2.times do |i|
061 j = i % n
062 k = (j+1+rand(9)) % 10
063 pool[j],pool[k] = pool[k],pool[j]
064 end
065 pool[0,n]
066 end
067 end # class NumberSequenceGuess
プライベートメソッドをオブジェクトのメソッドとして使おうとするとエラーになります.
require 'nsg' s = NumberSequenceGuess.new(4) s.generate_sequence(2,4) # ==> private method `generate_sequence' called for ... (NoMethodError)
クラス定数
クラス定義の中で定数を定義することができます. その定数はクラス内ではそのまま利用できます. またクラス定義の外部でも「クラス名::定数名」とすることで,その定数を参照することができます.
class Foo CONST_FOO = "Foo!" CONST_FOO2 = 0 end Foo::CONST_FOO # ==> "Foo!" Foo::CONST_FOO2 # ==> 0
クラスメソッド
クラスにはインスタンスに関連づけられたメソッド以外にも,クラスに関連した処理を行うクラスメソッドを定義できます. たとえば,Fileクラスにはファイルの状態を調べるクラスメソッドが多数用意されています. ここで例を示します.
File.exist?(file) # fileは存在するか? File.writable?(file) # fileに書き込みが許可されているか?
クラスメソッドは,次のように定義されます.
class クラス名 def クラス名.メソッド名(引数1,引数2,...) end end
なお後で説明するselfを使うと次のように定義できます. こちらの方がスマートです.
class クラス名 def self.メソッド名(引数1,引数2,...) end end
なおnewメソッドもクラスメソッドです. newメソッドはクラスに自動的に定義されます.
クラスのネスト定義
クラス定義の中でさらに別のクラスを入れ子にして定義できます. このようなクラスは,継承によって作成するサブクラスとは異なるものです. 入れ子で定義された内部のクラスには,その外側のクラスのメソッド,変数,定数などは受け継がれません.
このような仕組みはクラス同士の関係を構造化するのに使うことができます. 内部のクラスは「外側のクラス::内側のクラス」という形式の名前をもつことになります.
class Foo CONST_FOO = "Foo!" def foo_bar() Bar::CONST_BAR # 内部クラスBarの定数を参照する end # クラスFoo::Barの定義 class Bar CONST_BAR = "Bar!" def bar_foo() Foo::CONST_FOO end end end Foo::CONST_FOO # ==> "Foo!" Foo::Bar::CONST_BAR # ==> "Bar!" f = Foo.new b = Foo::Bar.new f.foo_bar # ==> "Bar!" b.bar_foo # ==> "Foo!" Foo::Bar::CONST_FOO # ==> NameError b.foo_bar # ==> NoMethodError
例題でもNumberSequenceGuessにGuessMatchという内部クラスを定義しています.
069 # NumberSequenceGuessの内部クラスとして
070 # 「予想チェック」のためのクラスGuessMatchを定義する
071 # 正解と予想を比較して桁と数字が一致した数(exact_match),
072 # それらを除いて,数字のみが一致した数(other_match)を調べる
073 class NumberSequenceGuess
074 class GuessMatch
:
144 end # class GuessMatch
145 end # class NumberSequenceGuess
クラスの拡張
既存のクラスに手を加えて,新しいメソッドを追加してクラスを拡張することもできます. クラスの拡張は継承により新しいサブクラスを作成するのとは異なります.
既存のクラスに新しいメソッドを追加するには,新しいクラスを定義する場合と 全く同様に「class...end」内でメソッドを定義します. 指定するクラス名がこれまでにないものであれば新しいクラスを作ることになり,既存のクラスであれば,そのクラスへの定義の追加となるわけです.
class クラス名
# 新しいメソッドの定義を記述する
end
なお,既存のメソッドと同じ名前を使ってメソッドの定義を行うとそのメソッドの定義を書き換えることになります.
例題でもStringクラスにメソッドto_nseqを追加しています. またNumberSequenceGuessクラスの定義を二つに分けて,GuessMatchという内部クラスを定義する部分を独立させています. これも形式的には一度NumberSequenceGuessクラス定義を終わって,その後GuessMatchの定義を追加していることになります. これらの定義は一つにまとめても問題ないのですが,読みやすさを考慮して二つに分けています.
002 # 文字列クラスの拡張
003 class String
004 # 数字列を「数字の配列」に変換するメソッド
005 # (例) "1234" --> [1,2,3,4]
006 def to_nseq
007 self.scan(/\d/).collect { |s| s.to_i }
008 end
009 end
010
011 # 「数字列予想ゲーム」のクラス
012 class NumberSequenceGuess
:
067 end # class NumberSequenceGuess
:
073 class NumberSequenceGuess
074 class GuessMatch
:
144 end # class GuessMatch
145 end # class NumberSequenceGuess
self
例題のプログラムで文字列クラスにto_nseqメソッドを追加定義しました. そこにselfという変数が現れています. selfはどこにも定義が見当たりませんが,これは何でしょうか. 「self」(自身)という名前から推測される通り,これは(定義しているインスタンスメソッドの対象である)インスタンス自身を示します. つまりselfとはこのto_nseqを起動したインスタンスそのものです. selfのメソッドを起動することは自分自身のメソッドを起動することです.
002 # 文字列クラスの拡張
003 class String
004 # 数字列を「数字の配列」に変換するメソッド
005 # (例) "1234" --> [1,2,3,4]
006 def to_nseq
007 self.scan(/\d/).collect { |s| s.to_i }
008 end
009 end
例題のプログラムの他のインスタンスメソッドの定義でも(同じクラスの)別のメソッドを使っていますが,selfのようにレシーバを明示しないで関数的メソッドとして呼び出しています.
021 # 予想チェックの結果を得る
022 def info(guess)
023 exact,rel = matched_info() # selfのmatched_infoメソッドを起動
024 [exact,rel,@n-exact-rel]
025 end
:
033 # 予想のチェック結果を返す
034 def matched_info
035 @gmatch.retrieve
036 end
このようにインスタンスメソッドから同じインスタンスのメソッドを呼び出すとき,selfは省略可能です. 上のStringクラスの例の場合も,実はselfは省略可能です. ただし次のようにselfが必要となる場合もあります.
class String def same?(other) self == other end end "abc".same?("abc") # ==> true "abc".same?("ABC") # ==> false
なおクラスの定義(class ... end)の直下のレベルではselfはそのクラスです(Rubyのクラスはオブジェクトで,クラスもselfで参照できます).
その他の話題
ここでは,例題のプログラムで使っていて今回のトピックには直接関係のない文法事項を紹介します.
alias
同じメソッドをいろいろな名前で使えると便利です. Rubyでは,aliasによりメソッドに別名をつけることができます.
048 # メソッドの別名定義
049 alias equal? match? # equal?をmatch?の別名とする
050 alias correct? match? # correct?をmatch?の別名とする
別名をつけたメソッドは,別名でも元の名前でも呼び出すことができます.