OneDay

40歳からプログラマとして生活しています。

Ruby歴半年くらいの自分へ HelloWorld (後編)

nograve.hatenadiary.jp
の続き。attr_writer, attr_accessorについて。

#sample8
class HappyBirthday
	attr_reader :name, :birthday_month, :birthday_date
	def initialize(name="Alice", birthday_month=1, birthday_date= 1)
		@name = name
		@birthday_month = birthday_month
		@birthday_date = birthday_date
	end
end

このコード例でattr_writerについて書いていく。pryでやっていく。

[1] pry(main)> load './mynote.rb' #ファイル名がmynote.rb
=> true
[2] pry(main)> alice = HappyBirthday.new
=> #<HappyBirthday:0x007fc5d3dd0220 @birthday_date=1, @birthday_month=1, @name="Alice">
[3] pry(main)> alice.birthday_date
=> 1
[4] pry(main)> alice.birthday_date =2
NoMethodError: undefined method `birthday_date=' for #<HappyBirthday:0x007fc5d3dd0220>
from (pry):4:in `__pry__'

HappyBirthday.newを叩くと引数に何も指定していないため、initializeメソッドで設定した内容が返る。それをaliceというオブジェクトに代入している。しかし、誕生日を1月2日に変更したいという場合、どうしたら良いか。sample8の例ではbirthday_dateに書き込みできるメソッドがないため、alice.birthday_date = 2と打つとundefined method 'birthday_date='と出る。このエラーを解消するには、まさにbirthday_date=というメソッドを書けばよい。

[5] pry(main)> class HappyBirthday
[5] pry(main)*   def birthday_date=(integer)
[5] pry(main)*     @birthday_date=integer
[5] pry(main)*   end
[5] pry(main)* end
=> :birthday_date=
[6] pry(main)> a.birthday_date =2
=> 2
[7] pry(main)> a
=> #<HappyBirthday:0x007fc5d3dd0220 @birthday_date=2, @birthday_month=1, @name="Alice">

これでbirthday_dateに書き込みが可能となった。しかし、nameやbirthday_monthも変更することがあるかもしれない。その場合はname=メソッド、birthday_month=メソッドを定義すれば良いが、同名のインスタンス変数(@nameや@birthday_month)に値を入れるだけのメソッドを書くのは無駄に思える。この無駄を省くため、attr_readerと同様にattr_writerというアクセサが用意されている。下記のようにattr_writerをセットする。

[9] pry(main)> class HappyBirthday
[9] pry(main)*   attr_writer :name, :birthday_month, :birthday_date
[9] pry(main)* end
=> nil
[10] pry(main)> alice.name = 'Iris'
=> "Iris"
[11] pry(main)> alice.birthday_month =2
=> 2
[12] pry(main)> alice
=> #<HappyBirthday:0x007fc5d3dd0220 @birthday_date=2, @birthday_month=2, @name="Iris">

attr_writerでインスタンス変数の書き込みができることが分かった。attr_readerと合わせれば読み書きができそうである。

class HappyBirthday
	attr_reader :name, :birthday_month, :birthday_date
	attr_writer :name, :birthday_month, :birthday_date
end

これでもプログラムは動くが、冗長であるため、attr_accessorというものが用意されている。

class HappyBirthday
	attr_accessor :name, :birthday_month, :birthday_date
end


最後にinitializeメソッドについてだが、代表的なRubyの標準クラスでinitializeメソッドを持つものを上げるとこんな感じ

[5] pry(main)> String.new
=> ""
[6] pry(main)> Array.new
=> []
[7] pry(main)> Hash.new
=> {}

これらのクラスは箱を用意してくれる。Arrayクラスなんかは初期値を下記のように設定することもできる。

[1] pry(main)> a = Array.new
=> []
[2] pry(main)> b = Array.new(3, 2)
=> [2, 2, 2]
[3] pry(main)> c = [1, 2, 3, 4]
=> [1, 2, 3, 4]

では、attr_accessorだけのclassはnewメソッドは何が返るか、実験してみよう。

#sample9
class HappyBirthday
	attr_accessor :name, :birthday_month, :birthday_date
end
[1] pry(main)> load './mynote.rb'
=> true
[2] pry(main)> a = HappyBirthday.new
=> #<HappyBirthday:0x007f894591f188>
[3] pry(main)> a.name = "Alice"
=> "Alice"
[4] pry(main)> a.birthday_month = 1
=> 1
[5] pry(main)> a.birthday_date =1
=> 1
[6] pry(main)> a
=> #<HappyBirthday:0x007f894591f188 @birthday_date=1, @birthday_month=1, @name="Alice">

HappyBirthday.newの値をaに代入している。もちろんインスタンス変数の設定はしていないので、aオブジェクトの中身は空同然だ。aオブジェクトはHappyBirthdayクラスのattr_accessorで定義されたメソッドが使える。a.name = "Alice", a.birthday_month = 1, a.birthday_date =1と打っていけばすべてのインスタンス変数を埋めたaオブジェクトの完成である。しかし面倒くさい。

一度に設定可能なsettingメソッドを定義してみよう。

[7] pry(main)> class HappyBirthday
[7] pry(main)*   def setting(name, birthday_month, birthday_date)
[7] pry(main)*     @name = name
[7] pry(main)*     @birthday_month = birthday_month
[7] pry(main)*     @birthday_date = birthday_date
[7] pry(main)*   end
[7] pry(main)* end
=> :setting
[8] pry(main)> b = HappyBirthday.new
=> #<HappyBirthday:0x007f8945d3c478>
[9] pry(main)> b.setting('Bob', 2, 2)
=> 2
[10] pry(main)> b
=> #<HappyBirthday:0x007f8945d3c478 @birthday_date=2, @birthday_month=2, @name="Bob">

これで一度にインスタンス変数の値を更新することができたが、このメソッドはinitializeメソッドと変わりない。しかも最初にオブジェクトを生成し、オブジェクトのメソッドとしてsettingメソッドで設定する必要があり二度手間だ。initializeならばオブジェクトを生成する際に同時に各インスタンス変数の値を設定できる。オブジェクトにインスタンス変数の値をもたせる場合は、initializeメソッドを書かない積極的な理由はないと思われる。