Rubyのprotectedの使いどころ

Rubyのprivate/protectedは特殊で誰しもが混乱する設計の一つです。
そんなprotectedの使い所について書かれています。
 

Effective Ruby

Effective Ruby

項目14 protectedメソッドを使ってプライベートな状態を共有しよう

カプセル化が邪魔になるケース

他のオブジェクトの内部状態にアクセスしなければならない場合。
instance_eval使えばいけるけど、、、

class Widget
  # 他のobjectと座標が重なっているかチェックする場合など
  def overlapping?(other)
    x1, y1 = @screen_x, @screen_y
    x2, y2 = other.instance_eval { [@screen_x, @screen_y] }
  end
end

protectedで解決

class Widget
  def overlapping?(other)
    x1, y1 = @screen_x, @screen_y
    x2, y2 = other.screen_coordinates
  end

  protected
    def screen_coordinates
      [@screen_x, @screen_y]
    end
end

覚えておく事項

  • プライベートな状態はprotectedメソッドで共有できる
  • レシーバを明示してprotectedメソッドを呼び出せるのは、同じクラスのオブジェクトか共通のスーパークラスからprotectedメソッドを継承しているオブジェクトだけだ。

一応private/protectedのおさらい

privateはサブクラスからも呼べます。それってJavaでいうprotectedでは?と思いますが、 ここの違いは、インスタンス経由で呼べるかどうかです。
サブクラスのインスタンスから呼べるのがprotected、呼べないのがprivateと。
 

class Person
  protected
    def protected_method
      puts "Person#protected_method"
    end

  private
    def private_method
      puts "Person#private_method"
    end
end

class Runner < Person
  # ok(これはいいよね)
  def call_protected_method
    protected_method
  end

  # ok(これが呼べちゃう)
  def call_private_method
    private_method
  end

  # ok(インスタンスのprotectedなのでok)
  def call_instance_protected_method(instance)
    instance.protected_method
  end

  # ng(インスタンスのprivateなのでng)
  def call_instance_private_method(instance)
    instance.private_method
  end
end

>> r1 = Runner.new
>> r2 = Runner.new
>> r1.call_protected_method
Person#protected_method
>> r1.call_private_method
Person#private_method


>> r1.call_instance_protected_method(r2)
Person#protected_method
>> r1.call_instance_private_method(r2)
NoMethodError (private method `private_method' called for #<Runner:0x00007fed1a8818f0>

まとめ

プライベートな状態はprotectedメソッドで共有できることと、 privateは明示的なレシーバを用いて呼び出せないと覚えておけば良い。