Rubyでインスタンス変数を「プライベート」(C++またはJava定義)にする方法はありますか?言い換えると、次のコードでエラーが発生するようにしたいのですが。
class Base
def initialize()
@x = 10
end
end
class Derived < Base
def x
@x = 20
end
end
d = Derived.new
Rubyのほとんどのものと同様に、インスタンス変数は本当に「プライベート」ではなく、d.instance_variable_get :@x
を使用して誰でもアクセスできます。
ただし、Java/C++とは異なり、Rubyのインスタンス変数はalwaysprivateです。これらはpublicの一部ではありません。 APIのようなメソッドは、その詳細なゲッターでのみアクセスできるためです。APIに正気がある場合は、代わりにメソッドを使用するため、誰かがインスタンス変数を悪用することを心配する必要はありません。もちろん、誰かがワイルドになってプライベートメソッドやインスタンス変数にアクセスしたい場合、それらを停止する方法はありません。)
唯一の問題は、誰かが誤ってクラスを拡張するときにインスタンス変数を上書きするかどうかです。これは、例では@base_x
と呼ぶ可能性のない名前を使用することで回避できます。
インスタンス変数を直接使用しないでください。アクセサーのみを使用してください。次の方法で、リーダーをパブリック、ライターをプライベートとして定義できます。
class Foo
attr_reader :bar
private
attr_writer :bar
end
ただし、private
およびprotected
は、ユーザーが意図していることを意味するものではないことに注意してください。パブリックメソッドは、任意のレシーバーに対して呼び出すことができます:名前付き、自己、または暗黙的(x.baz
、self.baz
、またはbaz
)。プロテクトメソッドは、自分自身または暗黙的に(self.baz
、baz
)。プライベートメソッドは、暗黙のレシーバー(baz
)でのみ呼び出すことができます。
要するに、Ruby以外の観点から問題にアプローチしているということです。インスタンス変数の代わりに常にアクセサーを使用します。 public
/protected
/private
を使用して意図を文書化し、APIの消費者が責任ある大人であると想定します。
あなたが求めていることを正確に行うことは可能ですが(お勧めできません)
望ましい動作には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を使用する場合、実行時のコード置換は現実の事実であるという事実を理解する方が良いでしょう。
可視性のレベルが異なるメソッドとは異なり、Rubyインスタンス変数は常に(オブジェクトの外部から)プライベートです。ただし、内部オブジェクトのインスタンス変数には、親、子クラス、または含まれているモジュールから常にアクセスできます。
Rubyが@x
にアクセスする方法を変更する方法はおそらくないので、それを制御することはできないと思います。 @x
を作成すると、そのインスタンス変数が直接選択されるだけで、Rubyは変数の可視性制御を提供しないため、それと共存すると思います。
@marcggが言うように、派生クラスがインスタンス変数に触れたくない場合は、それをまったく使用しないか、派生クラスから見えないようにするための賢い方法を見つけてください。
インスタンス変数はクラスではなくオブジェクトによって定義されるため、希望することを行うことはできません。
継承ではなく構成を使用する場合、インスタンス変数の上書きについて心配する必要はありません。