Rubyはオブジェクト指向プログラミング言語であるから、当然のことながらクラスが定義できるのである。
そもそもクラスとは何かというと、ロールプレイングゲームにおけるキャラクタの商売のことではなくて、ソフトウェア工学的には、
「データとその操作手順をまとめた、オブジェクトの設計図にあたるもの」
というふうに定義されている。しかし、私は英語本来の意味の一つである、『生物学的な分類である綱』という表現も気に入っているのである。なぜなら、オブジェクト指向におけるクラスというのは、継承(あるクラスを基にして、別のクラスを作る)という機能によって、まるで生物の進化のごとき階層構造を作り上げるからだ。
Rubyは、スクリプト言語である。スクリプト言語というのは、どうしても『ひょいと作って、ちょろい仕事をさくさくっと終わらせる』という用途に使われることが多いから(個人ユースの場合)、まあわざわざクラスを定義することもなく、貴方の目的を達成することができるかもしれないが、ちょっと大きめのプログラムを作成する場合は、やはりクラスに出馬願った方が、整然としたものが出来上がる(はず)なのである。
Rubyでのクラス定義は、次のように記述する。
class クラス名 < [スーパークラス名]
end
オブジェクト指向プログラミング言語には、先ほど申し上げたように、継承という機能があって、あるクラスを基にして、新しいクラスを定義することができる。新しいクラスは、原則として元になったクラスが持つインスタンス変数やメソッド(クラスに定義されている関数をこう呼ぶ)などの『メンバ』を自らのものとし、使用したり、外部に公開することができるのである。無論、全て自分の力で物事を成し遂げたいときは、継承など利用しなくともよい。
それでは、実際にクラスを定義してみよう。
#犬クラスの定義
class Dog
end
犬のクラスを定義してみた。Rubyの場合、お約束としてクラス名はアルファベットの大文字で始めましょうというルールがある。試しに定義してみると、次のようなエラーが出てしまう。
『クラス及び
モジュール(※モジュールについては次章で解説する)名はコンスタントにしなければならない』というエラーである。即ち、定数として定義しなさいという意味で、定数だと、それを定義したクラス以下のすべてのクラス/モジュールから、スコープを超えて参照できるという特徴がある。逆にまあそうでないと使えない。
Rubyにおいては、クラスもまた、classというクラスのインスタンスなのであるが、通常classを定義すれば、そのインスタンスを生成して使用するのが望ましい。
pochi = Dog.new
これで、犬クラスのポチというインスタンスが生成されたことになる。だが、悲しいことにこのポチは、なにもすることができない、ただそこにある概念のみの存在なのだ。なぜかというと、Dogクラスが、なにも変数やメソッド、すなわちメンバを持っていないからなのだ。
それではまずインスタンス変数を持たせてみよう。
class Dog
@name #名前
@age #年齢
end
名前と年齢を持たせてみた。では次に、このインスタンス変数を使用して、自己紹介させてみたい。といっても、自発的にやってくれるわけもなく、ではどうするかというと、自己紹介メソッドを定義するのである。メソッドの定義は次のようにする。
def メソッド名[`(' [arg ['=' default]] ... [`,' `*' arg] [',' '&' arg]`)']
文
end
と、これだけではなんのことかよくわからないと思うので、実際にメソッドを定義しながら解説しよう。
まず、argすなわち引数を全く取らないメソッドが定義できる。この場合()も省略可能である。
def foo
puts "fooメソッドが実行されました"
end
次に、普通に引数を取るメソッドが定義できる。
def foo(arg0)
puts "引数(#{arg0})を引き渡されました"
end
さらに、引数が省略された場合に備え、規定値を設定しておくこともできる。
def foo(arg0="省略")
puts "引数(#{arg0})を引き渡されました"
end
この場合の動きは、実行して確認しておこう。次のコードをirb実行させてみる。
001 | def foo(arg0="省略")
002 | puts "引数(#{arg0})を引き渡されました"
003 | end
004 | puts foo
005 | puts foo("テスト")
4行目、メソッドfooの呼び出しで、引数を省略した場合、『引数(省略)を引き渡されました』と表示されているのを確認していただきたい。
Rubyでは、C言語でいうところの『可変個数引数』を簡単に実現することができる。要するに可変個の要素を持つ配列を引数として指定することができるのである。
def foo(*arg0)
arg0.each{ |i|
puts i
}
end
このように、引数名の先頭に*をつけると、これが配列オブジェクトであるとみなすのである。では、これもirbで、実際に動きを確認してみよう。
001 | def foo(*arg0)
002 | arg0.each{ |i|
003 | puts i
004 | }
005 | end
007 | foo("Perl", "Python", "Ruby")
いかがであろうか。呼び出し側は、取り立てて配列として意識していないにもかかわらず、メソッドの内部では、配列のように扱っているのがお分かりと思う。
最後に、引数名の先頭に&をつけるとどうなるであろうか。この機能はRuby独特なもので、最後の引数の直前に&があると、メソッドに与えられているブロックが手続きオブジェクトとして引数に渡されるのである。
実例を見てみよう。
001 | def foo(cnt, &block_arg)
002 | cnt.times { block_arg.call }
003 | end
004 | foo(3) { print "Ruby! " }
これをirbで実行してみると次のようになる。
さて、ではこれらの予備知識を元に、我が犬クラスにメソッドを定義してみよう。
001 | class Dog
002 | @name
003 | @age
004 | def selfIntroduction
005 | puts "ボクは#{@name}です。年齢は#{@age.to_s}歳です"
006 | end
007 | end
008 | pochi=Dog.new
009 | pochi.selfIntroduction
ところがこれを実行してみると、なにやらおかしな結果になる。
ボクはです。年齢は歳です
などと、ポチは謎の自己紹介をしてしまうのである。とまあこれは当然の結果だ。なぜならインスタンス変数@nameと@ageに何も値が設定されていないのだから。
それならばといって、いきなり次のような無茶をしてはならない。
001 | class Dog
002 | @name
003 | @age
004 | def selfIntroduction
005 | puts "ボクは#{@name}です。年齢は#{@age.to_s}歳です"
006 | end
007 | end
018 | pochi=Dog.new
009 | pochi.@name="ぽち"
010 | pochi.@age=3
011 | pochi.selfIntroduction
もし、貴方がC++言語やC#言語使いであられた場合、いったいこれのどこが無茶なのかと疑問に思われるかもしれないが、Rubyにおいては、クラス内のインスタンス変数に直接アクセスするということは許されない。ではどうすればよいかといと、メソッド経由でアクセスするのである。
#名前の設定
def setName(name)
@name=name
end
#名前の取得
def getname
@name
end
#年齢の設定
def setAge(age)
@age=age
end
#年齢の取得
def getAge
@age
end
このように、クラス内部の変数にアクセスするメソッドを業界ではゲッタ(Getter)およびセッタ(Setter)、まとめてアクセサ(acceser)と呼ぶのだが、いかにも当たり間のことをちまちま書かねばならず、非常に面倒だ。そこで、Rubyにはアクセサの簡略記法が準備されている。
attr_reader:変数名(から@を取り除いたもの)
attr_writer:変数名(から@を取り除いたもの)
attr_accessor:変数名(から@を取り除いたもの)
上から順に、読み取り専用、書き込み専用、そして読み書き両方の指定だ。我がDogクラスに定義した2つの変数は、どちらも読み書きできるようにしたいので、3番目のattr_accessorを使用する。
001 | class Dog
002 | attr_accessor:name
003 | attr_accessor:age
004 | @name
005 | @age
006 | def selfIntroduction
007 | puts "ボクは#{@name}です。年齢は#{@age.to_s}歳です"
008 | end
009 | end
010 | pochi=Dog.new
011 | pochi.name="ぽち"
012 | pochi.age=3
013 | pochi.selfIntroduction
これで、無事ポチは自己紹介を全うすることができるようになった。
しかし、ここで異議を唱える人々が出てきたのである。名前も年齢も、一度設定すればおそらくもう二度と書き換えることはあるまい。年齢に関しては、長いスパンで見れば勿論変化するわけだが、プログラムが実行される数ミリ秒の間に加齢するとは思えん。それに、アクセサとして定義すると、プログラム実行中、好きなように書き換えられてしまうではないか。これではとてもではないが、堅牢なアプリケーションとはいえないぞ。と。
オブジェクト指向プログラミング言語のクラスは、概ね『コンストラクタ』という特殊なメソッドを持っている。インスタンスが生成されるときに、必ず呼び出されることが約束されているので、ここで必要な初期設定を行うことになっているのである。特殊であるとはいえ、コンストラクタも立派なメソッドだから、名前を持っている。これはプログラミング言語によって違いがあるが、概ね、クラスと同じ名前か、もしくはnewという名前になっているのである。
さらにコンストラクタには引数を渡すことができるから、ここで、名前と変数を渡してやって、内部の変数に格納してもらえばよいのだ。
Rubyの場合も、このコンストラクタのメカニズムは存在するのであるが、コンストラクタ(new)ではなく、そこから呼び出されるintializeメソッド(イニシャライザ)をオーバーライドしましょうということになっている。
それでは、先ほどのコードをinitializeメソッドを定義して書き換えてみよう。
001 | class Dog
002 | def initialize(name, age)
003 | @name=name
004 | @age=age
005 | end
006 | def selfIntroduction
007 | puts "ボクは#{@name}です。年齢は#{@age.to_s}歳です"
008 | end
009 | end
010 | pochi=Dog.new("ぽち", 3)
011 | pochi.selfIntroduction
よし。これでだいぶすっきりしたと喜んでいたら、またしても異議を唱える者が出てきた。いわく、「私は長い間生きてきたが、いまだかつて『犬いぬ』というのを見たことがない」と主張するのである。
確かに、動物界脊索動物門哺乳綱ネコ目犬科犬属という動物は厳然として存在するわけだが、それでも『犬』というのは概念に過ぎないのだ。犬属の下には、様々な種がある。ブルドッグとか柴犬とかチワワとか。
であるから、我らがポチも、それら種からインスタンスを生成すべきなのだ。ではクラス設計はどうするかというと、犬という属が持つ基本的な特徴を定義した犬クラスを作り、それを継承して各種のクラスを定義し、例えば成犬になったときの吼え方など、違うところを個別に実装すればよいのである。
001 | #柴犬クラス
002 | class ShibaKen <Dog
003 | def bark
004 | puts "わんわん"
005 | end
006 | end
007 |
008 | #チワワクラス
009 | class Chihuahua <Dog
010 | def bark
011 | puts "きゃんきゃん"
012 | end
013 | end
014 |
015 | pochi = ShibaKen.new("ぽち",3)
016 | aiful = Chihuahua.new("アイフル",2)
017 | pochi.bark
018 | aiful.bark
これが即ち、継承ということなのであった。
さて、駆け足でクラスの解説をしてきたわけだが、最後に、Rubyに準備されている恐るべき機能のご紹介をしておこう。
それは、クラスの重複定義だ。
001 | class Foo
002 | def method1
003 | puts "method1 done"
004 | end
005 | end
006 | class Foo
007 | def method2
008 | puts "method2 done"
009 | end
010 | end
凡百のプログラミング言語であれば、このようなことをすれば、直ちにクラスFooが二重定義であると怒られてしまうが、Rubyでは許される。許されるどころか、クラスFooにメソッドmethod2が新たに追加されるのである。同じ名前のメソッドを定義すると、後勝ちでオーバーライドされる。
これは実に驚嘆に値する機能なのである。通常、あるクラスに機能を追加したい場合は、継承して新しいクラスを作るということをするわけであるが、Rubyのこのクラス重複定義機能を使えば、新しいクラスを導出することなしに、機能追加ができるのだ。いわば、『自らを継承する』ということになるのである。
このように、通常のプログラミング言語では考えられない機能が、実にさらりと実装されているところに、Rubyの凄みがあるといえよう。
最終更新:2008年12月12日 08:23