11. クラス

Rubyは、クラスベースのオブジェクト指向をサポートする言語です。
ここではオブジェクト指向の最も基本的な仕組みであるクラスについて説明します。

オブジェクト指向

オブジェクト指向 とは「モノ(インスタンス)」を中心に考える開発手法です。
オブジェクト指向プログラミングでは、インスタンスの元となるクラスを定義することによってプログラムを書きます。
プログラムが実際に動くときは、定義したクラスから生成されたインスタンスが相互に作用しながらソフトウェアの機能を実現します。

Note : オブジェクト指向がわかると何が良いの?

オブジェクト指向がわかると、より読みやすく、メンテナンスしやすいプログラミングが可能です。


前述の通り、オブジェクト指向プログラミングは「インスタンスの相互作用により機能を実現」します。
例えば、「あるクラスAのインスタンスaが、別のクラスBのインスタンスbのメソッドを呼び出す」場合を考えます。
このとき、aはbに対してどんな情報を渡せばどのような結果が返るかだけを知っていればよく、bの中でどのような処理をしているかを知る必要はありません。
これにより、それぞれのクラスは独立性を高める(他のクラスに影響を与えない)ことができるようになります。
同時に、それぞれのクラスの中の挙動はそのクラス自身が担保すればよいため、メンテナンスや機能拡張がしやすくなります。


さらに、クラスごとに明確な役割を与えることができるため、コードが大規模になってきても見通しがよくなります。

クラスとインスタンス

クラスとインスタンスには、以下のような関係があります。

名称 説明
クラス 「ある共通の性質を持つモノの集合」について属性(データ)振る舞い(メソッド)などを定義した設計図のようなもの。
インスタンス クラスをもとに生成された具体的なモノ。オブジェクトともいう。

例えば「自動車」をイメージしてみましょう。
自動車にはどんな属性や振る舞いがあるでしょうか。

属性

  • 大きさ
  • ナンバープレートの番号

振る舞い

  • 走る
  • クラクションを鳴らす
  • ライトを点灯する
  • 窓やドアを開閉する

これらの属性や振る舞いは自動車が共通して備えているものであり、自動車は一つのクラスであるといえます。
街中を走る自動車は、自動車クラスからつくられたインスタンスであると考えることができます。

オブジェクトの構造

今まで学んできた文字列や数値、配列などは、StringNumericArrayというクラスです。
RubyのすべてのクラスはBasicObjectというクラスが持つ振る舞いを受け継いでいます(クラスの継承)。
ArrayクラスやNumericクラスは、このBasicObjectクラスを継承したObjectというクラスに、さらに機能を追加する形で定義されています。
このとき、継承の元となったクラスをスーパークラス、継承先のクラスをサブクラスと呼びます。
例えば、Objectクラスは、BasicObjectクラスのサブクラス、Arrayクラスのスーパークラスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BasicObject
├ Object
│  ├ Array
│  ├ String
│  ├ Hash
│  ├ Regexp
│  ├ IO
│  │ └ File
│  ├ Dir
│  ├ Numeric
│  │  ├ Integer
│  │  ├ Float
│  │  ├ Complex
│  │  └ Rational
│  ├ Exception
│  └ Time
(略)

クラス・スーパークラスを調べる(class・superclass)

classメソッドを使用することで、オブジェクトの属するクラスを調べることができます。
また、superclassメソッドを使用することで、そのクラスのスーパークラスを調べることができます。
superclassメソッドを使って、上記のように継承されていることを確認してみましょう。

check_class.rb
1
2
3
4
5
6
7
8
9
10
11
puts "abc".class
#=> String

puts (0.1).class
#=> Float (0.1.classと書くと分かりにくいため、括弧を使用しています。)

puts "abc".class.superclass
#=> Object

puts "abc".class.superclass.superclass
#=> BasicObject

クラスが持つメソッドを調べる(methods)

クラスが持つメソッドは、methodsというメソッドで調べることができます。
また、このmethodsはインスタンスに対しても同様に使うことができます。
(ただし、privateメソッドと呼ばれる種類のメソッドは取得できないので注意してください。)

check_methods.rb
1
2
3
4
5
6
7
# クラスのmethods
p String.methods
#=> [:try_convert, :new, :allocate, :superclass, ...]

# クラスのmethods
p [1,2,3].methods
#=> [:to_h, :include?, :at, :fetch, :last, :push, ...]

クラスの定義

クラスは以下の書き方で定義することができます。クラス名には、大文字のアルファベットから始まるアッパーキャメルケースを用いるようにしましょう(コーディング規約)。

1
2
3
class ClassName
  #属性やメソッドの定義
end

インスタンスの生成

クラスからインスタンスを生成するにはnewメソッドを利用します。
このとき、クラスにinitializeメソッドが定義されていれば、それが実行されます。
インスタンスを生成する際の初期化などは、initializeメソッドで行いましょう。

generate_instance.rb
1
2
3
4
5
6
7
8
class ValLaboratoryMember
  def initialize
    puts "新しい社員を定義します。"
  end
end

member = ValLaboratoryMember.new  #インスタンスを作成
#=> 新しい社員を定義します。 ※自動的にinitializeメソッドが呼ばれる

インスタンス変数

クラス内で定義される@から始まる変数を、インスタンス変数といいます。
インスタンス変数の値は、インスタンスごとに固有です。
インスタンス変数として定義された変数は、「クラスの中であれば、メソッドのスコープを越えて値を参照したり、変更したりする」ことができます。
ただし、何もしなければインスタンスの外側からは直接アクセスできません。
これを実現するためには、アクセスメソッドを利用します。

アクセスメソッド 用途
attr_reader :field 「参照」のみ可能にする
attr_writer :field 「変更」のみ可能にする
attr_accessor :field 「参照」と「変更」を可能にする
instance_variables.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ValLaboratoryMember
  def initialize(number, name)
    @number = number
    @name = name
  end

  attr_reader :number  #参照のみ可能
  attr_accessor :name  #参照と変更が可能
end

# インスタンスを作成
member = ValLaboratoryMember.new(0, "まちなみ")
puts member.number  #=> 0
# member.number = 9999  #=> 変更ができないのでエラー(NoMethodError)になる

puts member.name #=> まちなみ
member.name = "マチナミ"
puts member.name #=> マチナミ ※変更が可能

インスタンスメソッド

クラス内で定義したメソッドは、そのクラスのインスタンスに対して呼び出すことができます。
このようなメソッドをインスタンスメソッドと呼びます。

instance_methods.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ValLaboratoryMember
  def initialize(number, name)
    @number = number
    @name = name
  end

  def information
    "#{@name}さんの社員番号は#{@number}番です"
  end

  attr_reader :number  #参照のみ可能
  attr_accessor :name  #参照と変更が可能
end

# インスタンスを作成
member = ValLaboratoryMember.new(0, "まちなみ")
puts member.information
#=> まちなみさんの社員番号は0番です

クラス変数

クラス内で定義される@@から始まる変数をクラス変数といいます。
クラス変数は、そのクラスから生成されたインスタンスすべてが共有できる変数です。
この変数もインスタンス変数と同様に、「クラスの中であれば、メソッドのスコープを越えて値を参照したり、変更したりする」ことができます。
クラス変数を扱う上での注意点として、クラス変数にはアクセスメソッドが適用できません
したがって、これらをクラス外部から参照するためには、アクセサと呼ばれるメソッドを定義する必要があります。

class_variables.rb
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
26
27
28
class ValLaboratoryMember

  @@member_count = 0 # 社員数を表すクラス変数

  def initialize(number, name)
    @@member_count += 1 # インスタンスが生成されるたびに1増やす
    @number = number
    @name = name
  end

  # クラス変数にアクセスするためのメソッド(アクセサ)
  def member_count
    @@member_count
  end

  attr_reader :number  #参照のみ可能
  attr_accessor :name  #参照と変更が可能
end

# 一つ目のインスタンスを作成
member1 = ValLaboratoryMember.new(0, "まちなみ")
puts member1.member_count
#=> 1

# 二つ目のインスタンスを作成
member2 = ValLaboratoryMember.new(999, "なつめ")
puts member2.member_count
#=> 2

クラスメソッド

クラス内に以下の形式で定義したメソッドは、そのクラスに対して呼び出すことができます(インスタンスからは呼び出せません)。

1
2
3
4
5
class ClassName
  def self.method_name
    # クラスメソッドの処理
  end
end

メソッドの定義に、self.というものが追加されていることに注目してください。
このselfというのは、「クラス自身」を表していて、このクラスにmethod_nameという名前のメソッドを追加という意味になります。
このようなメソッドをクラスメソッドと呼びます。

試しに、 class_variables.rb で定義したアクセサmember_countをクラスメソッドにしてみましょう。

class_methods.rb
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
26
27
28
29
30
31
class ValLaboratoryMember

  @@member_count = 0 # 社員数を表すクラス変数

  def initialize(number, name)
    @@member_count += 1 # インスタンスが生成されるたびに1増やす
    @number = number
    @name = name
  end

  # クラス変数にアクセスするためのクラスメソッド(アクセサ)
  def self.member_count
    @@member_count
  end

  attr_reader :number  #参照のみ可能
  attr_accessor :name  #参照と変更が可能
end

# 一つ目のインスタンスを作成
member1 = ValLaboratoryMember.new(0, "まちなみ")
puts ValLaboratoryMember.member_count
#=> 1

# 二つ目のインスタンスを作成
member2 = ValLaboratoryMember.new(999, "なつめ")
puts ValLaboratoryMember.member_count
#=> 2

# puts member1.member_count
#=> クラスメソッドはインスタンスからは呼び出せないため、NoMethodErrorになる

クラスの継承

クラスの継承とは、既存のクラスの振る舞い(メソッド)を受け継いで新たなクラスを作ることです。
基本的に、サブクラスは自身のスーパークラスのメソッドを呼び出すことが可能です。
例えば、自動車クラスを継承した「ハイブリッドカー」クラスというものを考えてみましょう。

ハイブリッドカーは自動車なので、「走る」「クラクションを鳴らす」「ライトを点灯する」「窓やドアを開閉する」という振る舞いは変わりません。
これに加えて、ハイブリッドカーは「充電する」という新たな振る舞いを備えています。
このことから、「ハイブリッドカークラスは自動車クラスのサブクラスである」と考えられます。

クラスの継承は、<記号を用いて次のように定義されます。

1
2
3
class SubClass < SuperClass
  #属性やメソッドの定義
end

このとき、SubClassのインスタンスはSuperClassで定義されたメソッドを呼び出すことができます。
また、アクセスメソッドなどの定義もサブクラスに引き継がれます。

inheritance.rb
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
26
27
28
29
30
class ValLaboratoryMember
  def initialize(number, name)
    @number = number
    @name = name
  end

  def information
    "#{@name}さんの社員番号は#{@number}番です"
  end

  attr_reader :number  #参照のみ可能
  attr_accessor :name  #参照と変更が可能
end

# ValLaboratoryMemberクラスを継承
class ValEnginnerMember < ValLaboratoryMember
  def initialize(number, name)
    @number = number
    @name = name
  end
end

# ValEnginnerMemberクラスのインスタンスを作成
member = ValEnginnerMember.new(0, "まちなみ")
puts member.information
#=> まちなみさんの社員番号は0番です

# アクセスメソッドの定義も引き継がれる
puts member.name
#=> まちなみ

サブクラスでスーパークラスと同名のメソッドを定義すると、振る舞いを上書きすることができます。
この仕組みをオーバーライドと呼びます。
オーバーライドを使うと、親子のクラスでメソッドの呼び出し方を変えることなく、そのクラスに合った振る舞いにできるという利点があります。
一方で、予期しない形でスーパークラスのメソッドを破壊してしまう危険もあるので、注意が必要です。

override.rb
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
26
27
28
29
30
31
class ValLaboratoryMember
  def initialize(number, name)
    @number = number
    @name = name
  end

  def information
    "#{@name}さんの社員番号は#{@number}番です"
  end

  attr_reader :number  #参照のみ可能
  attr_accessor :name  #参照と変更が可能
end

# ValLaboratoryMemberクラスを継承
class ValEnginnerMember < ValLaboratoryMember
  def initialize(number, name)
    @number = number
    @name = name
  end

  # informationメソッドを上書き
  def information
    "#{@name}さんは社員番号#{@number}番のエンジニアです"
  end
end

# ValEnginnerMemberクラスのインスタンスを作成
member = ValEnginnerMember.new(0, "まちなみ")
puts member.information
#=> まちなみさんは社員番号は0番のエンジニアです

演習問題

問1

以下は、先ほど作成したValLaboratoryMemberクラスです。
以下の問に従って、このクラスを拡張してみましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
class ValLaboratoryMember
  def initialize(number, name)
    @number = number
    @name = name
  end

  def information()
    "#{@name}さんの社員番号は#{@number}番です"
  end

  attr_reader :number  #参照のみ可能
  attr_accessor :name  #参照と変更が可能
end

問1-1

自分自身を表すインスタンス me を作成し、informationメソッドを実行してください。

1
2
puts me.information()
#=> {あなたの名前}さんの社員番号は{あなたの社員番号}番です

問1-2

ValLaboratoryMemberクラスの属性に、「生年月日」を表す文字列変数birthday(YYYY-MM-DD) と「入社後に習得したスキル(なんでも構いません)」を表す配列skillsを追加し、インスタンス生成時に登録できるようにしてください。
また、登録した内容を以下の形式で表示できるようにinformationメソッドを変更してください。

1
2
3
4
5
6
puts me.information()
#=>
# 社員番号: {あなたの社員番号}
# 名前: {あなたの名前}
# 生年月日: {あなたの生年月日(YYYY-MM-DD)}
# 所持スキル: ["ビジネスマナー", "HTML"] ※一例です。

問1-3

「新たに習得したスキル」を引数として、skillsにスキルを追加するインスタンスメソッドadd_skillを作成してください。

1
2
3
me.add_skill("Ruby")
p me.skills
#=> ["ビジネスマナー", "HTML", "Ruby"] ※一例です。