Rubyではインスタンス変数を直接使うよりattr_readerを使うべき2つの理由

「オブジェクト指向設計実践ガイド」と言う本を読んでいて、本書を読んでいくとインスタンス変数とattr_readerメソッドの違いとか使い道について興味を持ったので、まとめていきます。

最終的な結論は、「attr_readerメソッドを使った方が何かしらと便利」となったわけですが、その道なりをまとめていきます。

インスタンス変数とattr_readerの決定的な違いとは?

インスタンス変数とattr_readerの決定的な違いは、インスタンスの外側から参照できるかどうかと言うのが主に挙げられる。

例えば、以下のコードを見てみよう。

class Gear 
    
    def initialize(cog, chain)
        @cog = cog
        @chain = chain
    end

end

gear = Gear.new(2,10)

puts gear.cog

# undefined method `cog' for #<Gear:0x000055c7f997ae20 @cog=2, @chain=10> (NoMethodError)

上記のコードは間違いで、おそらくgearオブジェクトの@cog変数を参照しようとして書いたと思われるが、@cogの様なインスタンス変数はオブジェクトの外側から直接参照できないようになっている。

しかし、Gearクラスに以下の様にcogメソッドを用意してあげることで、@cog変数を取り出す事ができる。(この表現は厳密には違う)

class Gear 
    attr_reader :cog
     #    ↓と全く同じ
     #   def cog
     #      @cog
     #   end

    def initialize(cog, chain)
        @cog = cog
        @chain = chain
    end

end

gear = Gear.new(2,10)

puts gear.cog  # 2

上記の例が、Rubyにおけるインスタンス変数とattr_readerの違いとして良く挙げられるものだろう。

attr_readerを使うべき2つの理由

しかし、実際は以下の2つの点でインスタンス変数よりもattr_readerを使うべきだと考えている。

  • attr_readerを使うと変更が容易になる
  • タイプミスの時にミスに気づきやすい

以下は、詳しく説明していく。

attr_readerを使うと変更が容易になる

以下のコードは、2つのインスタンス変数に対してattr_readerを使っているものだ。

class Gear 
    attr_reader :cog, :chain

    def initialize(cog, chain)
        @cog = cog
        @chain = chain
    end

    def calc
        chain * cog
    end
end

上記のコードのcalcメソッドを以下の様に、メソッドではなくてインスタンス変数を直接参照する場合でも、問題なく動く。

    def calc
        @chain * @cog
    end

しかし、懸念すべき点は、「cogメソッドの動きを変えたい」と言う時にインスタンス変数だと変更の際に色々な事を考える必要が出てくるが、attr_readerメソッドを使ってcogメソッドを定義しておけば、cogメソッド内をいじるだけで変更できる、と言う強みがある。

例えば、以下の様に変更するのが用意だ。(下記の例はまだ単純なクラス構造なので良いが、@cogが使われる場面が多くなるほどattr_readerのありがたさが身に染みる。

class Gear 
    attr_reader  :chain

    def initialize(cog, chain)
        @cog = cog
        @chain = chain
    end
    
    def cog
       @cog * 100
    end

    def calc
        chain * cog
    end
end