private なsetter メソッドの使いかた
ruby の話です.
private なsetter を なぜかレシーバー付きで呼べるのですが,「そんなん使うの?」という声をときどき聞くのでメモっときます.
おさらい
private なsetter メソッドは,定義に反してself.var= :foo
のように呼べる
class Foo def test self.var = 1 self.var end private def var puts 'getter called' @var end def var=(var) puts 'setter called' @var = var end end Foo.new.test
setter(var=()
) は呼べますが,getter(var()
) は呼べません.
setter called test.rb:4:in `test': private method `var' called for #<Foo:0x007fea2208c550 @var=1> (NoMethodError) from test.rb:21:in `<main>'
参考
private に設定されたメソッドは関数形式でしか呼び出せません。
Ruby 2.1.0 リファレンスマニュアル - 呼び出し制限
parse.y (primary): "self[n]=x" can be legal even when "[]=" is private. changes submitted in [ruby-talk:63982]
parse.y (aryset): ditto.
parse.y (attrset): "self.foo=x" can be legal even when "foo=" is private.
private なsetter とか使うの?
本題ですが まれに使います.
インスタンス変数になにか代入したいが,直接アクセスしたくない場合とか.
- Mixin するModule 内で
- setter に少しロジック入れる場合
どちらも,setter をpublic にしたくないケースではprivate なsetter を好んで使っています.
例1: Mixin するModule 内で
- class
Foo
とBar
が共通の機能を持っている - その機能で使うインスタンス変数への代入は,おなじclass 内に制限したい
module Feature def chop @var end private def var=(var) @var = var end end class Foo include Feature def initialize self.var = :foo # BAD: @var = :foo end end class Bar include Feature def initialize self.var = :bar # BAD: @var = :bar end end
Module として切り出している機能に対し,インスタンス変数を直接触るのは 少し密結合になる気がするので,private なsetter でインターフェイスします.
例2: setter に少しロジック入れる
- インスタンス変数に代入するとき,なにか前処理をしたい (
.to_s
とか) - その代入は,おなじclass 内だけに制限したい
class Foo def kick(target) self.var = target # BAD: @var = target.to_s # do something end def slap(target) self.var = target # BAD: @var = target.to_s # do another thing end private def var=(var) @var = var.to_s end end
前処理は代入直前にしか使わない + DRY にしておきたいので,たぶんこんな形になります.