最終更新日:

Ruby入門 オブジェクト指向


  1. 第1章 オブジェクト指向とは
  2. 第2章 カプセル化
  3. 第3章 配列オブジェクト
  4. 第4章 継承
  5. 第5章 多態性

第1章 オブジェクト指向とは

オブジェクト指向(object-oriented)とは

操作対象すべてをオブジェクトで考える

考え方, と言えます. そして, システム全体の振る舞いを

オブジェクト同士の相互作用として考える

考え方, です.

オブジェクト(Object)は, 「もの」と訳されますがプログラミング言語の世界では次の二つの性質を持つものと定義されます.
  1. 属性, 状態(Property, Atitude)
  2. 振る舞い, 操作(Method, Operation)
また, オブジェクト同士の関係(Relation)によってシステム全体の振る舞いを構成していきます.

例えば, 私(人)であれば
  1. 属性: 名前, 年齢, 血液型, 国籍, etc.
  2. 振る舞い: 話す, 食べる, 歩く, 寝る, etc.
と言ったように定義することも可能です. 実際には名前や年齢に具体的な値が入り「人」が特定されます. 例えば私であれば

オブジェクト
属性
名前: Masa
国籍: 日本
振る舞い
話す
食べる

このようにオブジェクトがどのような属性と振る舞い持つかという定義のことをクラスといい, 特定された(実体化されたといいます)オブジェクトのことをインスタンスと呼びます.

<ポイント>
クラス
オブジェクトの定義
<書式>
class クラス名
 属性の定義
 操作の定義
end

それでは実際に Ruby でオブジェクトを作ってみます.
例題 1-1
class Human
 def initialize
  @name = "Masa"
 end
 def talk
  print "My name is ", @name, "\n"
 end
end

masa = Human.new
masa.talk



<メモ>
★ クラス名は先頭が必ず大文字にする必要あります.
★ この中には5つ覚えるべき事柄があります。
  1. 属性の定義はインスタンス変数(先頭に@がある変数)で行ないます. 上記の例では @name が属性です.
  2. 振る舞いの定義はメソッドで定義します. そのオブジェクトに対して行なえる操作になります. 上記の例では initialize メソッドと talk メソッドが定義されています.
  3. クラスは定義であって実際にオブジェクトを使うときには

    変数 = クラス名.new

    とする必要があります. クラスの定義に従い特定のオブジェクトを作り出すことをインスタンスの生成と言います. 代入された変数がそのインスタンスを指し示すものとなります.
  4. initialize メソッドはインスタンスが生成されたときにはじめに自動的に実行されるメソッドです. インスタンスの初期化などに使われます.
  5. 実際にメソッドを実行するときは

    インスタンス.メソッド

    とします.


ここで, もう一度オブジェクト指向について考えてみましょう。
単に, talk メソッドだけを定義した場合と, Human クラスを定義してその振る舞いとして talk メソッドを定義した場合とを比較してみましょう
例題 1-2
#メソッドだけ
def talk(name)
 print "My name is ", name, "\n"
end

talk "Masa"
talk "Konno"





続いて, クラスを定義した場合
例題 1-3
class Human
 def initialize(item)
  @name=item
 end
 def talk
  print "My name is ", @name, "\n"
 end
end

masa  = Human.new("Masa")
konno = Human.new("Konno");

masa.talk
konno.talk




<メモ>
★ どちらも, 実行結果は同じです.
 メソッドのみの時の talk "Masa" は, "Masa"という文字を表示する, といった機能的に振る舞いますが, クラスを使ったほうは, masa.talk というように[主語].[述語]的になっています.
★ つまり!!
 オブジェクト指向とは, 一歩抽象度を高めて, 動作の主体を明確にし, 自然言語に近い形で(人間の思考に近い形で)プログラミングができる, プログラミング手法ということができそうです. (そのかわり, 初期のプログラムタイプ量は増えますが, プログラム量が増えれば増えるほど精神的に楽にプログラムできるようになるはず, です.)

第2章 カプセル化

先の例題でもう一つ気が付いたことはないでしょうか.

そう!!

クラスを定義したときは, talk メソッド実行の際に引数を渡してあげていないのに適切に処理を行っています.
つまり, masa.talk のときはちゃんと「My name is Masa」で, konno.talk のときは「My name is Konno」とおなじ talk メソッドでも, インスタンスが違えば処理も違う, という点です. これは, そのメソッドが呼びだされたときにどう振舞えばいいかをオブジェクトが知っている, ということです.
結構重要なので, 青く囲ってもう一度書きます.

そのメソッドが呼びだされたときにどう振舞えばいいかをオブジェクトが知っている

オブジェクトは自分が今どういう状態であるか, という情報をインスタンス変数に記憶している, と言えます. そのため, 勝手にその状態を他人に変えられたら困るので, インスタンス変数はクラス内部のメソッドの中でしか値を書き換えることができません.

たとえば, 先の例では,

masa.@name = "konno"

などとすることはできません. これを情報を隠すということから, 情報隠蔽とかカプセル化などと呼ばれています。
でもどうしても名前を変えたいこともあるでしょう. そういう時は裁判所に…ではなくて, オブジェクト指向ではどうしても情報を書き換えたいときは, そのオブジェクトに情報を変更するようなメソッドを用意してあげます.

例題2-1 アクセスメソッド
class Human
 def initialize(item)
  @name=item
 end
 def talk
  print "My name is ", @name, "\n"
 end
 def rename(val)
  @name=val
 end
end

masa = Human.new("Masa")
konno = Human.new("Konno")

masa.talk
konno.talk

masa.rename("Takeshi")
masa.talk




<メモ>
★ このように, インスタンス変数にアクセスするメソッドのことをアクセスメソッドといい, インスタンス変数はメソッドを通して書き換えられます.
★ 逆に, メソッドを実行しない限りインスタンス変数が書き換わることがない(つまり, オブジェクトの状態が変化することはない)とも言えるでしょう.

アクセスメソッドはよく使われるので特有の書き方があります.
例題2-2
class Human
 def initialize(item)
  @name=item
 end
 def talk
  print "My name is ", @name, "\n"
 end

 attr_accessor: name

end

masa = Human.new("Masa")
konno = Human.new("Konno")

masa.talk
konno.talk

masa.name="Takeshi"

masa.talk

print masa.name




<メモ>
★ この他に, 読み出し専用のアクセスメソッドにするattr_reader、書き込み専用のアクセスメソッドにするattr_writerもあります。
★ この方法を使うと masa.name="Takeshi" というようにあたかも属性に代入しているかのようにメソッド定義をすることができます(実際には代入演算子はメソッドとして動作しています)
★ ただし, むやみやたらにアクセスメソッドを作るのはカプセル化を崩してしまうことにもなるので, 極力さけるべきです.

ここらへんでそろそろまとめておきましょう。

<ポイント>
アクセスメソッドとは
インスタンス変数操作用メソッド
<書式>
attr_reader (@を除いた)インスタンス変数名
attr_writer (@を除いた)インスタンス変数名
attr_accessor (@を除いた)インスタンス変数名


インスタンス変数まとめ
  1. @をつける
  2. クラス内のメソッド内で使う
  3. クラス内のメソッド間で共有される(インスタンスごとに生成される)
  4. クラス外からはアクセスメソッド経由で操作する
  5. initializeメソッドで初期化する

<メモ>
★ 通常, インスタンス(オブジェクト)内部の状態を記憶するために用いられる.

まとまったところで復習問題

問題2-1
空欄を埋めて, 完成させてください。
class Human
 def initialize(item)
  @age
  @name=item
 end
 def talk
  print "My name is ", @name, "\n"
  print "I am ", @age," years old\n"
 end
 attr_accessor:name
 :age
end

masa = Human.new("Masa")
masa.age = 20
masa.talk




<ヒント>
★ 二通り解答があります。

<メモ>
★ initialize メソッドで @age だけを書いてますが, この状態では @age の値は「nil」という値になっています. これは初期化されていないことを表す特別な値です. ですが通常は initialize メソッドで @age の値も初期化するべきです(ここでは復習問題のためあえてアクセスメソッドを定義しています).


第3章 配列オブジェクト

Ruby ではすべてオブジェクトとして扱われています. 変数や配列も例外ではありません. ということはつまり, それぞれのデータを操作するためのメソッドが多数用意されています. ここでは配列を例にいくつかよく使うメソッドを紹介します. (もっと良く知りたい場合は
Ruby リファレンス 配列逆引きRuby 配列等を参照すると良いと思います)

問題3-1
配列の要素追加
a = Array.new
a.push 5
a.push 3
a.push 1
p a

b = []
b << 5
b << 3
b << 1
p b




<メモ>
★ 空の配列を作るには Array クラスのインスタンスを生成する(Array.new)ことで実現できます.
★ a = Array.new というのは a = [] というのを実行しているのと同じになります.
★ push メソッドは配列の末尾に要素を追加するメソッドです. << 演算子も push メソッドと同じ結果になります.

問題3-2
配列から要素取り出し
a = [1,2,3]
b = a.shift
p a
p b




<メモ>
★ shift メソッドは配列の先頭要素を取り出すメソッドです.

問題3-3
配列を整列
a = [5, 2, 3, 6, 1, 4]
b = a.sort
p a
p b
a.sort!
p a




<メモ>
★ sort メソッドは整列(ソート)した配列を返すメソッドです. 逆順に整列したいときは reverse メソッドを使います.
★ sort メソッドを実行しても元の配列の順序は変わりません. それに対して sort! は元の配列の並びを変えてしまいます. このようにメソッドを実行したオブジェクトの状態が書き変わるメソッドのことを Ruby では破壊的メソッドと呼んでいます. それに対して sort のようにオブジェクトの状態を書き換えないメソッドのことを非破壊的メソッドと呼んでいます.

問題3-4
配列の各要素への処理
a = [1, 2, 3]
a.each do |i|
 print i
end

print "\n"

a.each {|i| print i}




<メモ>
★ これは繰り返しの制御文のところで紹介した each メソッドです. 配列の各要素を取り出してプロック引数 i に渡しながら逐次処理をしていきます.
★ do end もしくは {} で囲まれた部分をブロックと言います. do end と {} はほぼ同じですが, 複数行に渡る処理の場合は do end, 一行で済む処理の場合は {} が使われる傾向があるようです. ブロックについては 4 ページ目 Ruby 特有の仕様 で詳しく解説します.

問題3-5
配列要素の抽出
a = [1, 2, 3, 4, 5]
b = a.select {|i| i%2==0}
p b




<メモ>
★ select は条件にあう要素だけを含む配列を返します. % は剰余算演算子といって整数割り算の余りを計算する演算子です. つまり, 「2 で割った余りが 0 かどうか」という比較演算を行なっています. これは「偶数かどうか」ということチェックしているのと同じでよく使われる計算式です. i%2!=0 とすれば「奇数かどうか」をチェックできます.

次は少し応用です. 配列に自分の定義したクラスのインスタンスを格納してみます.
問題3-6
複数インスタンスの一括処理
class Human
 @@group_name = ""

 def initialize(item)
  @name=item
 end

 def group_name=(name)
  @@group_name = name
 end

 def group_name
  @@group_name
 end

 def talk
  print "My name is ", @name, "\t"
  print "A member of ", @@group_name, "\n"
 end
end

member = []
member << Human.new("Masa")
member << Human.new("Take")
member << Human.new("Oliv")

member[0].group_name = "Rubyists"

member.each do |who|
 who.talk
end





<メモ>
★ 配列 member に Human クラスのインスタンスを配列要素として追加しています.
配列[n].メソッド とすることで各インスタンスのメソッドを呼び出すことが可能です.
★ 先頭に @@ がついている変数はクラス変数と呼ばれるもので, インスタンス間で共有されます. member[0] の group_name しか設定していないにもかかわらず, 他のインスタンスでも同じように @@group_name の値 ("Rubyists") を呼び出せています.
def メソッド名=( 仮引数) とメソッド定義するとあたかもメソッドに値を代入するかのようにアクセスメソッドを定義できます. (参照: Ruby リファレンス メソッド)


このようにクラスを定義しなくても Ruby には便利なメソッドが多数用意されているのでそれを活用するだけでも十分効率の良いプログラミングができます. ある程度 Ruby の仕様が理解できたら
  1. どのようなオブジェクトがあるのか
  2. そのオブジェクトにはどのようなメソッドが用意されているのか
Ruby リファレンスマニュアルを見て調べておくのが良いでしょう.


第4章 継承

クラスを定義していると似たようなクラスがいくつも作成されることがあります. 同じ部分を共有して異なる部分だけを別に新しく定義できるとクラス定義を再利用できて効率が良いでしょう. この仕組みを提供するのが継承という機能です.

例題4-1
class Human
 def initialize(item)
  @name=item
 end
 def talk
  print "My name is ", @name, "\n"
 end
end


class Japanese < Human
 def hello
  print "私の名前は ", @name, " です\n"
 end
end

masa = Japanese.new("Masa")
masa.talk
masa.hello




<メモ>
★ クラス Japanese に talk というメソッドが宣言されていないにもかかわらず Japanese クラスのオブジェクトである masa のメソッドとして実行されています.
★ これを「talk メソッドを継承している」といいます.
★ このとき Japanese クラスの継承元となった Human クラスをスーパークラス, 継承してできた Japanese クラスをサブクラスといいます.

まとめると
<ポイント>
継承
サブクラスにメソッドを受け継ぐ
<書式>
class サブクラス名 < スーパークラス名
 メソッド定義
end

<メモ>
★ 継承をする利点は, すでにあるクラスを拡張して新しいクラスを定義できる点です.
★ 逆に欠点としては, スーパークラスに変更があった場合にはサブクラスに影響が出る, という点です.

次は, サブクラスで同じ名前のメソッドを再定義した場合の動作です.
例題4-2
class Human
 def initialize(item)
  @name=item
 end
 def talk
  print "My name is ", @name, "\n"
 end
end

class Japanese < Human
 def talk
  print "私の名前は ", @name, " です\n"
 end
end

masa = Japanese.new("Masa")
masa.talk




<メモ>
★ サブクラスでスーパークラスと同じ名前のメソッドを定義するとサブクラス側の定義が有効になります.
 これをメソッドのオーバーライド(再定義)と言いいます。

次は, Array クラスに新しいメソッドを付け加えてみます.
例題4-3
class NewArray < Array
	def sum		# 合計値の計算
		self.inject(0.0){|result, item| result + item}
	end
	def ave		# 平均値の計算
		self.length/sum
	end
end

a = NewArray.new
a << 1 << 2 << 3 << 4 << 5
p a
p a.sum
p a.ave




<メモ>
★ inject([init]){|result, item| ...} はまず init と配列の先頭要素をブロック引数の result と item に渡してブロックを実行し, 次にそのブロックを実行した結果を result に, 次の配列要素を item に渡してまたブロックの処理をする, といったことを要素の終わりまで繰り返し実行します. 結果として上記の例では配列要素の合計値を求める計算になっています(参照:
Ruby リファレンス Enumerable).
★ self はインスタンスそれ自身を表す変数です. Java や C++ では this という予約語が使われています.
★ length は配列の要素数を返すメソッドです.
★ 少し難しいかもしれませんが, この合計と平均を求めるメソッドを覚えておくと便利です.
★ ただし, sum や ave メソッドが呼ばれるたびに要素すべてを合計するという処理が繰り返されるので効率は良くありません.
★ もし継承しなくても良いという場合は直接 Array クラスにメソッドを追加しても良いでしょう.

例題4-4
class Array
	def sum		# 合計値の計算
		self.inject(0.0){|result, item| result + item}
	end
	def ave		# 平均値の計算
		self.length.to_f/sum
	end
end

a = [1, 2, 3, 4, 5]
p a
p a.sum
p a.ave





第5章 多態性

この継承の機能をうまく使うと, プログラムは驚くほど知的な行動をしてくれます.
まずは以下の例題を見てみてください.
例題5-1
class Human 
 def initialize(item)
  @name=item
 end
 def talk
  print "My name is ", @name, "\n"
 end
end

class Japanese < Human
 def talk
  print "私の名前は ", @name," です\n"
  live
 end
end

def whoareyou(who)
 who.talk
end

nancy = Human.new("Nancy")
masa = Japanese.new("Masa")

whoareyou nancy
whoareyou masa




<メモ>
★ポイントは, 同じ whoareyou メソッドを実行してもオブジェクトの種類によって, 振る舞いが異なっています(違うメソッドが実行されている).
 引数として動作している who は代入されたオブジェクト(のクラス)によっていろいろな振る舞いをするということから, このような機能を多態性, ポリモルフィズム, 動的結合などと呼んでいます.
★ つまり
そのメソッドが呼びだされたときにどう振舞えばいいかをオブジェクトが知っている
と言えます.

例題5-2
class Human 
 def initialize(item)
  @name=item
 end
 def talk
  print "My name is ", @name,"\n"
 end
end

class Japanese < Human
 def talk
  print "私の名前は ", @name, " です\n"
end


member = []
member << Human.new("Nancy")
member << Japanese.new("Masa")
member << Japanese.new("Konno")

member.each do |who|
 who.talk
end




<メモ>
★ member 配列には Human クラスのインスタンスと Japanese クラスのインスタンスが混在していますが, 一括して処理をしているにもかかわらず, クラスの種類によって違うメソッドが実行されています.
★ もしこれを多態性の機能を使わないで実現しようとすると次のように条件分岐を使う必要があります.

例題5-3
class Human
 def initialize(name, nationality)
  @name=name
  @nationality=nationality
 end
 attr_reader :nationality

 def talk_english
  print "My name is ", @name, "\n"
 end
 def talk_japanese
  print "私の名前は ", @name, " です\n"
 end
end


member = []
member << Human.new("Nancy", "American")
member << Human.new("Masa", "Japanese")
member << Human.new("Konno", "Japanese")

member.each do |who|
 if who.nationality == "American"
  who.talk_english
 elsif who.nationality == "Japanese"
  who.talk_japanese
 end
end




<メモ>
★ 動作は多態性を使った場合と同じです. しかし, こちらはインスタンスを判別するために @nationality という属性を追加し, 条件分岐を使って別々のメソッドを呼び出しています.
★ もし @nationality の種類が増えた場合は条件分岐の数とメソッドの数を増やす必要があります. 多態性を使えば, 新しくクラスを追加するだけで済むわけです.


まだまだオブジェクト指向は奥が深いですが基本的な部分ということでこれで終わりにします. もし Ruby とオブジェクト指向に興味がある場合は次のサイト/本などを参考にしてはいかがでしょうか. 特にデザインパターンを勉強すると継承と多態性の効率の良い使い方をマスターできると思います. またクラス間の関係やメソッド呼び出しのタイミング等を図的に表現するUML(Unified Modeling Language) を学ぶと一層オブジェクト指向のイメージが膨らみ意味をつかみやすいと思います.
  1. Matz 日記 オブジェクト指向は難しい
  2. まつもと直伝 プログラミングのオキテ
  3. Rubyによるデザイン・パターン
  4. デザインパターンを読み解く
  5. Ruby で学ぶオブジェクト指向入門
  6. UML 超入門
次は Ruby の正規表現について解説します.