🥟

【Ruby】attr_accessor・ゲッター・セッターについて掘り下げて考えた

に公開

自己紹介

はじめまして、はると申します。完全異業種からのエンジニア転職を目指して学習をしています。

概要

Rubyの attr_accessor について、わかりやすく解説されている記事はすでにたくさんありますが、自分が理解できるまで掘り下げたことをまとめました。

注意

私は前職が完全異業種であり、スクールに入って初めてプログラミングに触れました。
そんな自分が理解しづらかった部分を、同じように初めてプログラミングの概念に触れた人に向けてまとめました。
自分の復習も兼ねて、超超噛み砕いて書いているため、周りくどい書き方になっている箇所もあるかと思います🙇

ゲッターとセッターとは

こちらの記事が大変わかりやすかったので引用させていただきます🙇

https://qiita.com/k-penguin-sato/items/5b75be386be4c55e3abf

こちらの記事で全て理解できた方は今回の記事は対象外となります。
  
一応、公式リファレンスからも引用すると、それぞれ下記のように解説されています。
attr_reader:インスタンス変数 name の読み取りメソッドを定義します。1)
attr_writer:インスタンス変数 name への 書き込みメソッド (name=) を定義します。2)
attr_accessor:インスタンス変数 name に対する 読み取りメソッドと書き込みメソッドの両方 を定義します。3)

噛み砕いて考える

ゲッターとセッターについてはなんとなくわかりましたが、以下の疑問が残りました。

  • ゲッターを追加した後に、それを呼び出すときに使われているメソッド名はどの部分で決めた名前が使われている?
  • ゲッターとセッターの解説内によく出てくるinitializeメソッドはなぜ必要?
  • 実行順はどうなっている?😵‍💫

この疑問を解決するため、どのような順番でコードが実行されているのか考えてみました。
下記のRubyのコードがあったとして、実行順に見てみます。

class MeatBun            # 肉まんクラス
  attr_reader :filling
  attr_writer :filling
  def initialize(filling)
    @filling = filling   # fillingは具
  end
end

bun1 = MeatBun.new('豚肉')
bun1.filling = '鶏肉'
p bun1.filling

  1. bun1 = MeatBun.new('豚肉')実行時
    MeatBunクラスのinitializeメソッド(新しいインスタンスが作成されたときに自動的に呼び出されるメソッド)が呼び出されます。
    initializeメソッドの引数として'豚肉'が渡されて、@filling'豚肉'が入っている状態でMeatBunのインスタンスが生成され、それがbun1に代入されています🐖
    この段階ではattr_readerattr_writerは介していません。
  2. bun1.filling = '鶏肉'実行時
    attr_writer により生成された filling=(val) メソッドが呼び出されます。
    元々'豚肉'が入っていた@fillingに、引数で渡した'鶏肉'を再代入します🐓
      
    下記のメソッドが、attr_writer :fillingの1行で表現されているということです。
    valという言葉を使っているのは、公式リファレンスの通りにしました。)
    def filling=(val)
      @filling = val
    end
    
    def croissant=(val)
      @croissant = val
    end
    
  3. p bun1.filling実行時
    attr_readerにより生成されたfillingメソッドが呼び出されます。
    そして@fillingが取得されます。
    先ほど@fillingの中に、'鶏肉'を再代入していたため、'鶏肉'が表示されます。
      
    下記のメソッドが、attr_reader :fillingの1行で表現されているということです。
    def filling
      @filling
    end
    

attr_accessorについて

上記のようにattr_readerattr_writerを両方使う場合は、attr_accessorの1行で済みます。

class MeatBun
  attr_accessor :filling  # ここ
  def initialize(filling)
    @filling = filling
  end
end

疑問は解決できたか?

  • bun1.filling = '鶏肉'で使われていた.fillingの部分は、attr_writerで生成されたfilling=(val)メソッドのことでした。
  • initializeメソッドがないとインスタンス生成時には@fillingが無いので、bun1 = MeatBun.new('豚肉')のように、初めから値を渡してインスタンス作成することはできません。
    ですが、initializeが無くても、bun1 = MeatBun.newと引数無しでインスタンスを生成することはできます。attr_writerattr_readerがあれば、引数無しでインスタンス生成後にbun1.filling = '鶏肉'p bun1.fillingで値の代入や参照が可能です。
  • 実行順番を整理したことで、
    最初に@fillingへの値の代入を行っているのはinitializeメソッド、
    値の更新を行っているのはattr_writerで定義されたメソッド、
    値を参照しているのはattr_readerで定義されたメソッド、
    とそれぞれ別々のメソッドで行われていることがわかりました。
  • また、下記の3つのメソッドでは同じ名前のインスタンス変数@fillingが使われていますが、同じクラス内の異なるメソッドで同じ名前のインスタンス変数を使用している場合、それらが同じものと見なされるということがわかりました。(だから値の更新や参照をしても同じ@fillingに対して行えます)
    class MeatBun
      def filling        # attr_reader :filling
        @filling
      end
    
      def filling=(val)  # attr_writer :filling
        @filling = val
      end
    
      def initialize(filling)
        @filling = filling
      end
    end
    

さいごに

細かく噛み砕いたことでずっと苦手意識を持っていたゲッターやセッターの概念、attr_accessorについての理解が進みました!
ここまで読んで頂きありがとうございました🙂

参考・引用記事

引用記事

1)Ruby 3.3 リファレンスマニュアル attr_reader
2)Ruby 3.3 リファレンスマニュアル attr_writer
3)Ruby 3.3 リファレンスマニュアル attr_accessor

参考記事

Discussion