はじめに

03 こんにちはRubyでは、「Rubyはオブジェクト指向言語である」という説明をしました。これから、オブジェクト指向の考え方を使ってプログラミングをしていくわけですが、初めて触れる概念なので、少しオブジェクト指向そのものについて、手を動かしながら学んでいきましょう。

オブジェクトとは

オブジェクト指向をかんたんに説明すると、オブジェクト(Object, モノ)を中心にしてプログラムを開発していく手法のことです。オブジェクトとは、データと処理をひとまとめにしたものです。オブジェクト指向の考え方を適用したプログラミングを、オブジェクト指向プログラミング(OOP; Object Oriented Programming)と呼びます。

ある配列のそれぞれの要素を画面に表示するという処理を、オブジェクト指向言語では無いC言語と、オブジェクト指向言語であるRubyでそれぞれ書いてみましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main(void)
{
        int i;
        int ary[] = { 2, 3, 5, 7, 11, 13, 17, 19 };

        for (i = 0; i < 8; i++) {
                printf("%d\n", ary[i]);
        }

        return 0;
}
1
2
3
4
ary = [2, 3, 5, 7, 11, 13, 17, 19]
ary.each do |item|
  puts item
end

C言語では、配列は単なるデータに過ぎないので、for文なりwhile文なりの処理を使って操作していく必要があります。

一方、オブジェクト指向言語Rubyにおいては、配列はデータと処理がひとつになったモノ=オブジェクトであるので、aryという配列オブジェクトのデータを、eachメソッドを用いて処理をすることができます。

クラスについて

04 Rubyクイックツアーでは、「クラス」はC言語でいう型であり、オブジェクトの種類を表すという説明をしました。

オブジェクト指向について説明する時は、「クラス」はよく「設計図」であるという説明をされます。この説明の考え方を適用すると、1や2といった整数オブジェクトは、整数(IntegerクラスもしくはFixnumクラス)という設計図から作られた実体です。”takuya”や”hello”といった文字列オブジェクトは、Stringクラスという設計図から作られた実体です。クラスから作られた実体の事を、オブジェクト指向プログラミングの用語で「オブジェクト」と呼びます。

クラスは設計図であり、その設計図からオブジェクトが生成される、という考え方は、以下のコードから読み取れます。

list = Array.new
puts list
list.push(2)
list.push(3)
puts list

hash = Hash.new
puts hash
hash[:key] = 'value'
puts hash

クラスを定義する

整数クラスや文字列クラス、配列クラス、Hashクラスなど、Ruby側ですでに用意されているクラス以外にも、自分でクラスを作ることができます。

自動車を表す、Carクラスを定義してみましょう。

1
2
3
4
5
6
7
8
9
class Car
  def initialize(name)
    @name = name
  end

  def display_name
    puts @name
  end
end

定義したCarクラスから、オブジェクトを生成してみましょう。

1
2
3
4
5
6
7
car1 = Car.new('プリウス')
car1.display_name
#=> プリウス

car2 = Car.new('パジェロ')
car2.dipslay_name
#=> パジェロ

クラスの定義は、以下のように行います。

class クラス名

end

また、定義されたクラスからオブジェクトを生成するには、以下のようなコードを書きます。

obj = クラス名.new()

インスタンスメソッドについて

クラスは、データと処理をまとめたものだと説明しました。まずは処理に着目しましょう。オブジェクト指向プログラミングを行うためには、あるクラスについて、そのクラスから生成されたオブジェクトに、何をさせたいかを記述する必要があります。その何をさせたいかの処理は、メソッドとして記述します。

ただし、クラス内に定義するメソッドは特別に「インスタンスメソッド」と呼ばれ、通常のメソッドとは区別されます。通常のメソッドは一度定義してしまえばどこからでも呼び出せるのに対して、インスタンスメソッドはそのクラスから生成されたオブジェクトからしか呼び出すことができません。

class クラス名
  def メソッド名(引数1, 引数2, ...)
  end
end

Person(人間)クラスを定義し、与えられたメッセージを喋るsayというインスタンスメソッドを定義したいときは、以下のようなコードがかけます。Personクラスを定義したあと、オブジェクトを生成しsayメソッドを実行しています。

1
2
3
4
5
6
7
8
class Person
  def say(message)
    puts message
  end
end

takuya = Person.new 
takuya.say('こんにちは')

インスタンス変数について

次に、データに着目しましょう。オブジェクトの中でデータを保持しておくために利用されるのが、「インスタンス変数」です。

インスタンス変数の変数名は「@」で始まります。

class クラス名
  def メソッド名
    @name = 値
  end
end

例えば、Personクラスに苗字を格納するインスタンス変数@last_nameと、名前を格納するインスタンス変数@first_nameを定義してみましょう。

1
2
3
4
5
6
7
8
9
class Person
  def set_first_name(first_name)
    @first_name = first_name
  end

  def set_last_name(last_name)
    @last_name = last_name
  end
end

インスタンス変数はクラス内の全メソッド内で共通して利用することができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person
  def set_first_name(first_name)
    @first_name = first_name
  end

  def set_last_name(last_name)
    @last_name = last_name
  end

  def say_my_name
    puts "わたしは#{@last_name}#{first_name}です。"
  end
end

takuya = Person.new
takuya.set_first_name('卓矢')
takuya.set_last_name('向平')
takuya.say_my_name
#=> わたしは向平卓矢です。

また、インスタンス変数は、クラスから生成されるオブジェクトごとに固有の値となります。以下のコードを見てわかるように、同じPersonクラスから生成したオブジェクトどうしでも、内部で保持しているインスタンス変数の値は違います。

1
2
3
4
5
6
7
8
9
10
11
12
takuya = Person.new
takuya.set_first_name('卓矢')
takuya.set_last_name('向平')

fukkun = Person.new 
fukkun.set_first_name('耕太朗')
fukkun.set_last_name('福地')

takuya.say_my_name
#=> わたしは向平卓矢です。
fukkun.say_my_name
#=> わたしは福地耕太朗です。

なお、クラス内のインスタンスメソッドで定義された変数でも、@をつけないとただのローカル変数となり、そのメソッド内でしか有効にならない、という事は忘れないでください。(C言語のスコープを思い出すこと)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student
  def set_seiseki(number)
    seiseki = number
  end

  def print_seiseki
    puts seiseki
  end
end

st = Student.new    
st.set_seiseki(60)
st.print_seiseki
#=> エラーになる

initializeメソッド

インスタンスメソッドの中でも、”initialize”と名付けられた特殊なインスタンスメソッドがあります。このメソッドをクラス内に記述した場合には、オブジェクトが作成される時(つまり、クラス名.newが呼び出された時)に自動的に呼び出されます。

class クラス名
  def initialize
    処理
  end
end

initializeメソッドを使うことで、オブジェクトを作成する時に必ず実行したい処理を確実に実行することができます。多くの場合、initializeメソッドで引数を取り、その値をインスタンス変数に代入しておく、というような処理に使われます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Dog
  def initialize(name, type, color)
    @name = name
    @type = type
    @color = color
  end

  def display_info
    puts "名前: #{@name}"
    puts "犬種: #{@type}"
    puts "色: #{@color}"
  end
end

pochi = Dog.new('ポチ', '柴犬', '黒')
pochi.display_info

アクセスメソッド

通常、クラスの中で使われているインスタンス変数には、外から値を参照したり変更したりすることができません。インスタンス変数を参照したい場合も、変更したい場合も、インスタンスメソッドを通して行う必要があります。(以下のコード参照)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Cat
  def initialize(name, age)
    @name = name
    @age = age
  end

  def display_info
    puts @name
    puts "#{@age}才"
  end

  def set_age(age)
    @age = age
  end
end

tama = Cat.new('タマ', 2)
puts tama.name
#=> エラー発生
puts tama.age
#=> エラー発生
tama.age = 3
#=> エラー発生
tama.set_age(3)
tama.display_info

このような挙動を示すのは、オブジェクト指向プログラミングにおける「カプセル化」の考え方があるためです。インスタンス変数に直接アクセスできると危険なため、インスタンスメソッドを通してインスタンス変数の操作を行います。通常、set_○○やget_○○といったインスタンス変数を用意します。それによって、インスタンス変数にセットされるデータの検証などを行うことができます。

とはいえ、すべてのインスタンス変数に対してset_○○やget_○○というインスタンスメソッドを準備するのはめんどうなので、Rubyではインスタンス変数の参照や更新が簡易的に行えるように、アクセスメソッドというものが用意されています。

用意されているアクセスメソッドは、以下の3つです。

定義式 機能
attr_reader :var_name インスタンス変数@var_nameの参照が可能になる
attr_writer :var_name インスタンス変数@var_nameの更新が可能になる
attr_accessor :var_name インスタンス変数@var_nameの参照・更新が可能になる

使い方は、次のとおりです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class School
  def initialize(name, num_of_students)
    @name = name
    @num_of_students
  end
 
  attr_reader :name
  attr_accessor :num_of_students
end

tnct = School.new('苫小牧高専', 925)
puts tnct.name
puts tnct.num_of_students
tnct.num_of_students = 900
puts tnct.num_of_students

練習問題

1. 自分の好きなものをクラスにしてみる

自分の好きなもので、たくさんあるものをクラス化して、インスタンス変数やインスタンスメソッドを定義して、遊んでみましょう。