はじめに
メソッドの定義や使い方をコードリーディングしていると、「これは何のメソッドだろう?」と迷うことがよくあります。自分自身、インスタンスメソッドとクラスメソッドの違いをあまり意識せずに読んでることもありました。
この記事では、インスタンスとは何かという基本から振り返り、インスタンスメソッドとクラスメソッドの違いを簡単に解説します。メソッドの定義方法や、具体的な呼び出し方を確認しながら、理解を深めていきます。
目次
インスタンスとは
プログラム上でオブジェクトのひな形(設計図)がクラスです。
クラスからオブジェクトをつくることをインスタンス化といいます。
そして、クラスから作られた(インスタンス化した)オブジェクトのことをインスタンスといいます。
大事なので図で説明します。
↑↑↑↑これがインスタンスです!!
- インスタンスは具体的な個々のオブジェクトを指す
- インスタンスは、クラスで定義されたインスタンスメソッドを呼び出すことができる。
- 継承したクラスのインスタンスは、スーパークラスで定義されたインスタンスメソッドを呼び出すことが出来る。
- インスタンス変数を保持している。
インスタンスメソッドの定義の仕方
では、次にインスタンスメソッドの定義の仕方を確認します。
class Animal
def initialize(name)
@name = name
end
def speak
"#{@name} makes a sound"
end
end
このコードでは、Animal
クラスを定義し、initialize
メソッドでインスタンス変数 @name
を初期化します。speak
メソッドがインスタンスメソッドであり、インスタンスごとに異なる動作を行います。
インスタンスメソッドの使い方
class Animal
def initialize(name)
@name = name
end
def speak
"#{@name} makes a sound"
end
end
# Animalクラスのインスタンス化とインスタンスメソッド呼び出しの例
dog = Animal.new("Dog")
puts dog.speak # => Dog makes a sound
cat = Animal.new("Cat")
puts cat.speak # => Cat makes a sound
<クラス名>.new(引数)
でインスタンス化することが出来ます。
作成したインスタンスに.メソッド名
を付けてあげることでクラスで定義したインスタンスメソッドを使えるようになります。
今回の例だと、Animal.new("Dog")
は Animal
クラスのインスタンスを生成し、dog.speak
でインスタンスメソッドが呼び出され、インスタンス変数 @name
にアクセスします。
インスタンスメソッドとインスタンス変数
インスタンスメソッドをもう少し理解するためにメソッドの探索経路についてのお話をします。
インスタンスメソッドをきちんと理解するためには継承を無視することはできません。インスタンスは作成元のクラスに定義してあるインスタンスメソッドだけではなく、親クラスのメソッドも使えるからです。
インスタンスメソッドを実行すると、そのインスタンスが属するクラスに指定されたメソッドが存在するかどうかを判定します。属するクラスにメソッドがない場合は、さらにひとつ抽象度を上げたスーパークラスにメソッドを探しに行きます。
そして最後まで見つからなかった場合は、NoMethodError
が発生するという感じです。
そしてちなみにの話をすると、メモリ上でインスタンスメソッドは、クラスオブジェクトに、インスタンス変数はインスタンスに保持されます。
インスタンスメソッドはクラスオブジェクトのメソッドテーブルに格納され、インスタンスがメソッド呼び出しを行う際に、メソッド探索がクラスのメソッドテーブルで行われます。
#クラスオブジェクトで呼び出し
p Animal.instance_methods
# => [:hash, :singleton_class, :dup, :itself, :methods,
#:singleton_methods, :protected_methods, :private_methods,
#:public_methods, :instance_variables, :instance_variable_get,
#:instance_variable_set, :instance_variable_defined?,
#:remove_instance_variable, :instance_of?, :kind_of?, :is_a?,
#:display, :public_send, :class, :tap, :frozen?, :yield_self,
#:then, :extend, :clone, :<=>, :===, :!~, :method, :public_method,
#:nil?, :singleton_method, :eql?, :respond_to?,
#:define_singleton_method, :freeze, :inspect, :object_id, :send,
#:to_s, :to_enum, :enum_for, :!, :equal?, :__send__, :==, :!=,
#:__id__, :instance_eval, :instance_exec]
#インスタンスで呼び出し
p p dog.instance_methods
#(NoMethodError)
#クラスオブジェクトで呼び出し
p Animal.instance_variables
# => []
#インスタンスで呼び出し
p dog.instance_variables
# => [:@name]
インスタンス変数は、インスタンスごとに個別に保持されるデータです。インスタンスメソッドは、そのインスタンスが保持するインスタンス変数にアクセスして、データを操作します。
見にくいですが、下の図のような感じになります。
クラスメソッドの定義の仕方
class Animal
@@count = 0
def initialize(name)
@name = name
@@count += 1
end
def self.get_count
"Total animals: #{@@count}"
end
end
このコードでは、self.get_count
というクラスメソッドを定義しています。このメソッドはクラス全体に対して動作します。
クラスメソッドはインスタンスメソッドと異なり、メソッド名にself.
を付けます。
クラスメソッドの使い方
# クラスメソッド呼び出しの例
class Animal
@@count = 0
def initialize(name)
@name = name
@@count += 1
end
def self.get_count
"Total animals: #{@@count}"
end
end
puts Animal.get_count # => Total animals: 0
# インスタンスを作成するとカウントが増える
lion = Animal.new("Lion")
tiger = Animal.new("Tiger")
puts Animal.get_count # => Total animals: 2
-
self.get_count
はクラスメソッドであり、インスタンスを生成せずに呼び出せます。 - クラスメソッドは、
Animal.get_count
のようにクラスから直接呼び出します
とりあえず、クラスメソッドはインスタンスメソッドと異なり、インスタンス作成を必要としません。
クイズ
animals = Animal.all
Q : このとき、animals
はインスタンスですか?
理由付きで答えられるでしょうか?
考えてからスクロールしてみて下さい。
答えはNOです。
animals
自体はインスタンスではなく、コレクション(ActiveRecord::Relation
オブジェクト)で、配列の各要素が Animal
インスタンスです。(ActiveRecord::Relation
に関してはまた別の記事を書こうと思います。)
Ruby on Rails の ActiveRecord モデルクラスで、<クラス名>.all
がクラスメソッドとして定義されていて、クラスオブジェクトに対して呼び出すと、インスタンスの配列が返されるようになっています。
そのため、animals
でインスタンスメソッドを呼び出そうとすると、エラーが発生します。このときのanimals
はActiveRecord::Relation
オブジェクト(Array
のように振る舞う)だからです。いくらクラスを遡っても、speak
メソッドは見つかりません。
一応確認すると、
# animals は ActiveRecord::Relation オブジェクト
animals = Animal.all
puts animals.class # => ActiveRecord::Relation
コレクションに対しては、each でループすることで個々のインスタンスにアクセスできるようになります。
animals.speak
NoMethodError: undefined method `speak' for #<Animal::ActiveRecord_Relation:0x00007f9d4a1d2b10>
# 各インスタンスのメソッドを呼び出し
animals.each do |animal|
puts animal.speak
end
あくまで、インスタンスメソッドは個別の情報(インスタンス変数)を持ったインスタンスをレシーバーとして呼び出すことが出来るのであり、インスタンスコレクションは対象になりません。
まとめ
- インスタンス:クラスから生成されたオブジェクトのこと
- インスタンスメソッド: インスタンスをレシーバーとして呼び出すメソッドです。主にインスタンス変数を操作し、個々のオブジェクトの状態に依存する処理を行う
- クラスメソッド: クラス自体をレシーバーとして呼び出すメソッドです。インスタンスの状態には依存せず、クラス全体に関わる処理を行う
端的な言葉で説明できるか否かは理解度の大きな判断基準になりそうです。
インスタンス・インスタンスメソッド・クラスメソッドの理解の助けになれば幸いです。
参考文献