28

Ruby でプライベートメソッドがどのように機能するかを理解するのにしばらく時間がかかりました。プライベートメソッドをそのまま処理する正当な理由があるかどうかは誰にもわかりませんか? 歴史的な理由だけですか?それとも実装理由?それとも、確かな論理的理由 (意味論的理由) がありますか?

例えば:

class Person
  private
  attr_reader :weight
end

class Spy < Person
 private
  attr_accessor :code
 public
  def test
    code          #(1) OK: you can call a private method in self
    Spy.new.code  #(2) ERROR: cannot call a private method on any other object
    self.code     #(3) ERROR!!! cannot call a private method explicitly on 'self'
    code="xyz"    #(4) Ok, it runs, but it actually creates a local variable!!!
    self.code="z" #(5) OK! This is the only case where explicit 'self' is ok
    weight        #(6) OK! You can call a private method defined in a base class
  end
end
  • 行 (1)、(2)、および (5) での Ruby の動作は妥当に見えます。
  • (6) が問題ないという事実は、特に Java と C++ から来ると、少し奇妙です。これには正当な理由がありますか?
  • (3) が失敗する理由がよくわかりません。説明、誰か?
  • (4) 行の問題は、'private' とは関係のない文法の曖昧さのように見えます。

何か案は?

4

1 に答える 1

34

ruby のpublic、private、および protectedの定義を読むと役立つ場合があります。(アクセス制御にスキップ)

Ruby の private は、Java の protected に似ています。Java のプライベートに相当する Ruby はありません。編集:このソリューションは、Ruby オブジェクトで Java のプライベートの理想を偽装する方法を提供するようになりました。

Private は、暗黙的にのみ呼び出すことができるメソッド/変数として定義されています。これが、ステートメント 2 と 3 が失敗する理由です。つまり、private は、メソッド/変数を、それらが定義されているクラスまたはサブクラスのコンテキストに制限します。継承は、プライベート メソッドをサブクラスに渡すため、暗黙的な自己でアクセスできます。(ステートメント6が機能する理由を説明しています。)

保護されたものに近いものを探していると思います。可視性が与えられていない Java アクセサー (例: public、private、protected) と同様に動作します。Spy の private を protected に変更すると、6 つのステートメントすべてが機能します。保護されたメソッドは、定義クラスまたはそのサブクラスの任意のインスタンスから呼び出すことができます。呼び出し元が呼び出しに応答するオブジェクトのクラスであるか、それから継承されている限り、self で明示的または暗黙的に呼び出されたものは、保護されたメソッドの有効なステートメントです。

class Person
  private
  attr_reader :weight
end

class Spy < Person
 protected
  attr_accessor :code
 public
  def test
    code          #(1) OK: you can call a private method in self
    Spy.new.code  #(2) OK: Calling protected method on another instance from same class family or a descendant.
    self.code     #(3) OK: Calling protected method on with explicit self is allowed with protected
    code="xyz"    #(4) Ok, it runs, but it actually creates a local variable!!!
    self.code="z" #(5) OK! This is the only case where explicit 'self' is ok
    weight        #(6) OK! You can call a private method defined in a base class
  end
end

s = Spy.new
s.test # succeeds
s.code #(7) Error: Calling protected method outside of the class or its descendants.

ステートメント 4 については、あいまいさを避けるためであると仮定して正しいです。これは、Ruby の動的な性質の潜在的な害に対する安全策です。後でクラスを再度開いてアクセサーをオーバーライドできないようにします。たとえば、汚染されたコードを評価することによって発生する可能性のある状況。

これらの動作につながった彼の設計上の決定については推測することしかできません。そのほとんどは、言語の動的な性質に帰着すると思います。

PS本当に物事にプライベートのJava定義を与えたい場合。サブクラスではなく、定義されているクラスでのみ使用できます。クラスに self.inherited メソッドを追加して、アクセスを制限したいメソッドへの参照を削除できます。

サブクラスから weight 属性にアクセスできないようにする:

class Person
  private
  attr_reader :weight

  def initialize
    @weight = 5
  end

  def self.inherited(subclass)
    subclass.send :undef_method, :weight
  end
end

class Spy < Person
 private
  attr_accessor :code
 public
  def test
     weight       
  end
end

Person.new.send(:weight)  # => 5
Spy.new.send(:weight)  #=> Unhelpful undefined method error

undef_method 呼び出しを次のように置き換える方が理にかなっているかもしれません:

  def self.inherited(subclass)
    subclass.class_eval %{
      def weight 
        raise "Private method called from subclass. Access Denied"
      end
     }
  end

これにより、はるかに役立つエラーと同じ機能が提供されます。

send は、他のクラスのプライベート メソッドの呼び出しを回避するために必要です。物事が実際に機能していることを証明するためにのみ使用されます。

後から考えると、これは非公開で保護されたものを役に立たなくします。メソッドの保護に真剣に取り組んでいる場合は、send をオーバーライドしてメソッドをブロックする必要があります。次のコードは、オブジェクトの private_methods に基づいてそれを行います。

def send_that_blocks_private_methods(method, *args)
  if private_methods.include?(method.to_s)
    raise "Private method #{method} cannot called be called with send."
  else
    send_that_allows_private_methods(method, *args)
  end
end

alias_method :send_that_allows_private_methods, :send
alias_method :send, :send_that_blocks_private_methods
private :send_that_allows_private_methods

すべてのプライベート メソッドへのアクセスを拒否する代わりに、アクセスをブロックする private_methods の class_variable を指定できます。send を非公開にすることもできますが、オブジェクトの外部から send を呼び出す正当な用途があります。

于 2009-10-14T11:10:21.863 に答える