2
0

More than 1 year has passed since last update.

クラスからメソッドを呼び出すことと、インスタンスからメソッドを呼び出すことの違いについて

Last updated at Posted at 2021-12-10

プログラミングを学び始めて1日目でも書けそうなコードで躓いてしまったのですが、やっぱり基本は大事だなと改めて思ったので、書き留めることにします。
皆さんは以下のコードを実行するとどのような結果になるかわかりますか。

class Human 

  puts "今から喋ります!"
  say

  def say
    puts "こんにちは"
  end
end

結果は以下のようにエラーとなります。

$ ruby human.rb
今から喋ります!
human.rb:4:in `<class:Human>': undefined local variable or method `say' for Human:Class (NameError)
from human.rb:1:in `<main>'

エラーコードを読んでエラー原因がわかった方も多いと思います。
なるほど、メソッドを先に定義していないからエラーになったんだ。と。
では、さっそく以下のように書き直しましょう!

class Human 

  def say
    puts "こんにちは"
  end

  puts "今から喋ります!"
  say
end

以下が結果です。

$ ruby human.rb
今から喋ります!
human.rb:8:in `<class:Human>': undefined local variable or method `say' for Human:Class (NameError)
from human.rb:1:in `<main>'

もうやだ!プログラミングやめる!!
このエラーを理解するためには、クラスから呼び出すメソッドと、インスタンスから呼び出すメソッドは勝手が違うということを理解する必要がありました。

1.クラスからメソッドを呼び出すときは、メソッドをクラスメソッドに定義する

以下のようにメソッド名に"self."を追加してメソッドをクラスメソッドにして実行してみます。

class Human 

  #before
#  def say
#    puts "こんにちは"
#  end
#
#  puts "今から喋ります!"
#  say
#end

  #after
  def self.say
    puts "こんにちは"
  end

  puts "今から喋ります!"
  say
end

実行結果:

$ ruby human.rb
今から喋ります!
こんにちは

無事にエラーが消えて、処理が実行されました!
この理由はself.をメソッド名の左側に明示的に記述することで、このメソッドのレシーバーにクラスを定義します。
(言い換えればインスタンスメソッドからクラスメソッドになります。)
レシーバーとは、メソッドの戻り値をレシーブ(受け取る)ところを指します。
【修正】レシーバーとは、メソッドの呼び出し元を指します。

class Class 

  def self.say
    p "#{self}がレシーバーです。"
  end

  say
end

以下の実行結果のようにself.にあたるレシーバーはクラスオブジェクトです。

$ ruby class.rb
"Classがレシーバーです。"

一方で、普段何気なく書いてる以下のようなメソッドはインスタンスメソッドです。
しかし、どこにもレシーバーを定義するself.の記述はありません。

def say
  puts "こんにちは"
end

インスタンスメソッドはself.が暗黙的に省略されてるだけで、実際には以下のような形が正確です。

def say
  self.puts "こんにちは"
end

では以下のようにインスタンス化して、インスタンスメソッドを呼び出してみます。

class Class
  def call
    p "#{self}がレシーバーです。"
    self.p "#{self}がレシーバーです。"
  end
end

class Main
  p instance = Class.new
  instance.call
end

実行結果を確認すると、selfがインスタンスIDになっていることが確認できました!

$ ruby instance.rb
#<Class:0x0000564fd89e0498>
"#<Class:0x0000564fd89e0498>がレシーバーです。"
"#<Class:0x0000564fd89e0498>がレシーバーです。"

2.プログラムは原則記述されたコードを上から順番に処理されていく

大前提ではあるのですが、気がついたらこの感覚がなくなってました。
最近はRailsを触ることが多く、プログラムのエントリーポイントとなるようなクラスを触ることがないので、記述する処理はメソッドとして書くことが多く、クラス内の実行順序を意識しなくなっていたようです。

3.インスタンスメソッドはメソッドを記述したクラス内の各メソッドの記述順序を気にしない。

上記2の話に繋がりますが、今回の学びで一番の気づきは3の部分です。

以下のように改修します。
1. 先程のsayメソッドを呼び出す処理をメソッド化(call)
2. 別クラスからHumanクラスをインスタンス化
3. callメソッドを呼び出す

class Human 

# before
#  puts "今から喋ります!"
#  say
#
#  def say
#    puts "こんにちは"
#  end

# after
  def call #1
    puts "今から喋ります!"
    say
  end

  def say 
    puts "こんにちは"
  end
end

class Main
  human = Human.new #2
  human.call #3
end

以下実行結果:

$ ruby human.rb
今から喋ります!
こんにちは

メソッド化する前は、sayメソッドを定義する前に同クラス内でsayメソッドを呼び出すとエラーになりました。
しかし、sayメソッドを定義する前にsayメソッドを呼び出すcallメソッドを定義した場合、別クラスでインスタンス化し、callメソッドを実行してもsayメソッドを呼び出すことが出来ます。

所感

この問題に行き当たった理由は、エントリーポイントとなるプログラムを書いていた際、よく使うメソッドを一連の実行処理のブロックの下にまとめていたのですが、実行してみるとエラーが発生したためです。
これまでなんとなく手続き型言語は1つのファイルを上から下に処理していくもので、オブジェクト指向言語は指定した変数やメソッドを探しだして実行するイメージを持っていましたが、プログラムというのは上から下に流れてくものだってことを改めて認識させられました。

あまりに初歩的で少し恥ずかしい失敗ではありましたが、大怪我する前に学ぶことが出来てよかったです…!

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0