web-dev-qa-db-ja.com

Rubyでインスタンス変数をプライベートにする方法は?

Rubyでインスタンス変数を「プライベート」(C++またはJava定義)にする方法はありますか?言い換えると、次のコードでエラーが発生するようにしたいのですが。

class Base
  def initialize()
    @x = 10
  end
end

class Derived < Base
  def x
    @x = 20
  end
end

d = Derived.new
34
prasadvk

Rubyのほとんどのものと同様に、インスタンス変数は本当に「プライベート」ではなく、d.instance_variable_get :@xを使用して誰でもアクセスできます。

ただし、Java/C++とは異なり、Rubyのインスタンス変数はalwaysprivateです。これらはpublicの一部ではありません。 APIのようなメソッドは、その詳細なゲッターでのみアクセスできるためです。APIに正気がある場合は、代わりにメソッドを使用するため、誰かがインスタンス変数を悪用することを心配する必要はありません。もちろん、誰かがワイルドになってプライベートメソッドやインスタンス変数にアクセスしたい場合、それらを停止する方法はありません。)

唯一の問題は、誰かが誤ってクラスを拡張するときにインスタンス変数を上書きするかどうかです。これは、例では@base_xと呼ぶ可能性のない名前を使用することで回避できます。

35
Josh Lee

インスタンス変数を直接使用しないでください。アクセサーのみを使用してください。次の方法で、リーダーをパブリック、ライターをプライベートとして定義できます。

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

ただし、privateおよびprotectedは、ユーザーが意図していることを意味するものではないことに注意してください。パブリックメソッドは、任意のレシーバーに対して呼び出すことができます:名前付き、自己、または暗黙的(x.bazself.baz、またはbaz)。プロテクトメソッドは、自分自身または暗黙的に(self.bazbaz)。プライベートメソッドは、暗黙のレシーバー(baz)でのみ呼び出すことができます。

要するに、Ruby以外の観点から問題にアプローチしているということです。インスタンス変数の代わりに常にアクセサーを使用します。 public/protected/privateを使用して意図を文書化し、APIの消費者が責任ある大人であると想定します。

26
Stephen Touset

あなたが求めていることを正確に行うことは可能ですが(お勧めできません)

望ましい動作には2つの異なる要素があります。 1つ目はx読み取り専用値に格納することで、2つ目はゲッターを保護するがサブクラスで変更されないようにすることです。


読み取り専用値

Rubyでは、初期化時に読み取り専用の値を格納できます。これを行うには、Rubyブロックのクロージャー動作を使用します。

class Foo
  def initialize (x)
    define_singleton_method(:x) { x }
  end
end

xの初期値は、ゲッター#xを定義するために使用したブロック内でロックされ、foo.xを呼び出す以外はアクセスできず、変更することはできません。

foo = Foo.new(2)
foo.x  # => 2
foo.instance_variable_get(:@x)  # => nil

インスタンス変数@xとしては保存されませんが、define_singleton_methodを使用して作成したゲッターを介して引き続き使用できます。


ゲッターの保護

Rubyでは、あらゆるクラスのほとんどすべてのメソッドを実行時に上書きできます。 method_addedフックを使用してこれを防ぐ方法があります。

class Foo
  def self.method_added (name)
    raise(NameError, "cannot change x getter") if name == :x
  end
end

class Bar < Foo
  def x
    20
  end
end

# => NameError: cannot change x getter

これは、ゲッターを保護するための非常に強引な方法です。

保護された各ゲッターをmethod_addedフックに個別に追加する必要があります。それでも、別のレベルのmethod_added保護をFooとそのサブクラスに追加して、コーダーを防止する必要がありますmethod_addedメソッド自体の上書きから。

Rubyを使用する場合、実行時のコード置換は現実の事実であるという事実を理解する方が良いでしょう。

13
user513951

可視性のレベルが異なるメソッドとは異なり、Rubyインスタンス変数は常に(オブジェクトの外部から)プライベートです。ただし、内部オブジェクトのインスタンス変数には、親、子クラス、または含まれているモジュールから常にアクセスできます。

Rubyが@xにアクセスする方法を変更する方法はおそらくないので、それを制御することはできないと思います。 @xを作成すると、そのインスタンス変数が直接選択されるだけで、Rubyは変数の可視性制御を提供しないため、それと共存すると思います。

@marcggが言うように、派生クラスがインスタンス変数に触れたくない場合は、それをまったく使用しないか、派生クラスから見えないようにするための賢い方法を見つけてください。

5
bryantsai

インスタンス変数はクラスではなくオブジェクトによって定義されるため、希望することを行うことはできません。

継承ではなく構成を使用する場合、インスタンス変数の上書きについて心配する必要はありません。

1
Andrew Grimm