何をインスタンス変数にするべきか(Ruby)

こちらの記事の補足。

masuyama13.hatenablog.com

インスタンス変数とは

インスタンス変数は、その名の通りインスタンス(オブジェクト)が持つ変数。インスタンスメソッド内で変数名の頭に@をつける。

インスタンスとオブジェクトはほとんど同じ意味。(詳しくはこちらの記事

def initialize(name, age)
  @name = name
  @age = age
end

同じクラスのインスタンスであっても、各インスタンスによって違う値を持つ。名前や年齢を考えてみればわかるように、偶然同じこともあるかもしれない。

変数のスコープ

変数には、スコープ(使える範囲)というものがある。

ローカル変数

メソッド内で定義した変数は、そのメソッド内でしか使うことができない。 反対に、メソッド外で定義した変数は、メソッドの中で使うことができない。

このような変数のことを、ローカル変数という。

class User
  def greet_ja
    my_name = "山田"
    puts "#{my_name}さん、こんにちは!"
  end

  def greet
    puts "Hello #{my_name}!"  # エラー!
    # greet_jaメソッドの外で、ローカル変数my_nameを使うことはできない
  end
end

インスタンス変数

一方、インスタンス変数のスコープは、インスタンス(オブジェクト)と同じになる。

同じインスタンスであれば、他のメソッドからでも呼び出して使うことができる。

class User
  def greet_ja
    @my_name = "山田"
    puts "#{@my_name}さん、こんにちは!"
  end

  def greet
    puts "Hello #{@my_name}!"
    # 他のメソッドからでも@my_nameを呼び出せる
  end
end

(ただし、@my_nameへの代入実行後(上の例ではgreet_jaメソッドの実行後)でないと@my_nameは空っぽになってしまうことに注意)

何をインスタンス変数にするべきか

結論から言うと、そのオブジェクト(インスタンス)の属性といえるものをインスタンス変数にするべきだと思う。

たとえば下のようなUserクラスを考える。

class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def greet
    puts "Hello #{@name}!"
  end

end

もしインスタンス変数を使わなかったら、greetメソッドは下のようになる。

# nameをメソッドの引数として渡してあげなければならない
def greet(name)
  puts "Hello #{name}!"
end

メソッドを実行するたびに毎回nameを引数で渡すとなると、面倒なだけでなく間違いの元にもなる。こういう値は、インスタンス変数としてインスタンス自身に持たせておくことで、必要なときにいつでも使えるようにしたほうがいい。

では、メソッドの引数になりそうなものを全部インスタンス変数にすればいいかと言えば、それは違う。

インスタンス変数を持つというのは、それぞれのオブジェクトがデータを持つということだ。

pメソッドを使って、自分で作ったクラスのオブジェクトを一度見てみると面白い。

class User
  def initialize(name, age)
    @name = name
    @age = age
  end
end

user = User.new("Anna", 20)
p user
#=> #<User:0x00007f84b6832330 @name="Anna", @age=20>

こんな感じで、インスタンス(オブジェクト)自身がインスタンス変数を保持していることがわかる。プログラムの意図と、各インスタンス自身が保持すべきデータかどうかを踏まえた上で、インスタンス変数にすべきかどうか考える必要がある。

属性とは

属性(ぞくせい)とは、英語でattribute(アトリビュート)といい、モノの特徴や性質という意味だ。

インスタンスメソッドを読み書きするためのアクセサメソッドは、頭にattrがついているが、このattrはattributeのことだろう。

attr_reader :name, :age