Ruby でプライベート属性を比較することはできますか?
-
11-09-2019 - |
質問
私は次のように考えています:
class X
def new()
@a = 1
end
def m( other )
@a == other.@a
end
end
x = X.new()
y = X.new()
x.m( y )
しかし、それはうまくいきません。
エラーメッセージは次のとおりです。
syntax error, unexpected tIVAR
では、同じクラスの 2 つのプライベート属性を比較するにはどうすればよいでしょうか?
解決
いくつかの方法があります。
ゲッターます:
class X
attr_reader :a
def m( other )
a == other.a
end
end
instance_eval
ます:
class X
def m( other )
@a == other.instance_eval { @a }
end
end
instance_variable_get
ます:
class X
def m( other )
@a == other.instance_variable_get :@a
end
end
私はルビーは簡単に周りにハッキングされた「友人」や「保護」のアクセス、さらには「プライベート」の概念を持っているとは思いません。ゲッターを使用すると、読み取り専用のプロパティを作成し、instance_evalをは含蓄が似ているので、あなたは、インスタンス変数の名前を知っている必要がありますを意味します。
他のヒント
当面の問題に対する適切な回答がすでにいくつかありますが、コメントが必要なコードが他にもいくつかあることに気付きました。(ただし、それらのほとんどは些細なことです。)
以下に 4 つの簡単な問題を示します。それらはすべてコーディング スタイルに関連しています。
- インデント:インデント用の 4 つのスペースと 5 つのスペースが混在しています。一般的には、次のことに固執する方が良いです 1つ インデントのスタイルであり、Ruby では通常 2 つのスペースです。
- メソッドがパラメータを受け取らない場合は、メソッド定義で括弧を省略するのが一般的です。
- 同様に、引数なしでメッセージを送信する場合、括弧は省略されます。
- ブロック内を除き、開き括弧の後と閉じ括弧の前に空白を入れません。
とにかく、それはほんの小さなことです。大きなことは次のとおりです。
def new
@a = 1
end
これはそうなります ない あなたがそう思うことをしてください!これにより、 実例 呼び出されるメソッド X#new
そして ない というクラスメソッド X.new
!
ここで何を呼んでいるのか:
x = X.new
です クラス 呼び出されるメソッド new
, から継承したものです。 Class
クラス。したがって、新しいメソッドを呼び出すことはありません。つまり、 @a = 1
決して実行されない、つまり @a
は常に未定義です。つまり、常に次のように評価されます。 nil
つまり、 @a
の self
そしてその @a
の other
常に同じになります、つまり m
常になります true
!
おそらくあなたがやりたいことはコンストラクターを提供することですが、Ruby にはそれがありません。 持っている コンストラクター。Ruby はファクトリ メソッドのみを使用します。
あなたがする方法 本当に オーバーライドしたいのは 実例 方法 initialize
. 。今、あなたはおそらく次のように自問しているでしょう。「なぜオーバーライドする必要があるのですか 実例 呼び出されるメソッド initialize
実際に電話をかけているとき クラス 呼び出されるメソッド new
?"
Ruby でのオブジェクトの構築は次のように行われます。オブジェクトの構築は 2 つのフェーズに分かれています。 割り当て そして 初期化. 。割り当ては、というパブリック クラス メソッドによって行われます。 allocate
, 、クラスのインスタンスメソッドとして定義されています。 Class
そして一般的には 一度もない オーバーライドされました。オブジェクトにメモリ空間を割り当て、いくつかのポインタを設定するだけですが、この時点ではオブジェクトは実際には使用できません。
そこでイニシャライザが登場します。というインスタンスメソッドです initialize
, 、オブジェクトの内部状態を設定し、他のオブジェクトが使用できる一貫した完全に定義された状態にします。
したがって、新しいオブジェクトを完全に作成するには、次のことを行う必要があります。
x = X.allocate
x.initialize
[注記:Objective-C プログラマはこれを認識しているかもしれません。]
ただし、電話するのを忘れやすいため、 initialize
一般的なルールとして、オブジェクトは構築後に完全に有効である必要があります。 と呼ばれる便利なファクトリ メソッドがあります。 Class#new
, これですべての作業が行われ、次のようになります。
class Class
def new(*args, &block)
obj = alloc
obj.initialize(*args, &block)
return obj
end
end
[注記:実は、 initialize
はプライベートであるため、次のようにリフレクションを使用してアクセス制限を回避する必要があります。 obj.send(:initialize, *args, &block)
]
最後に、何が問題なのか説明させてください。 m
方法。(他の人はすでに解決方法を説明しています。)
Ruby では、その方法はありません (注:Ruby では、インスタンスの外部からインスタンス変数にアクセスする「方法がない」は、実際には「リフレクションを伴う方法が常にある」という意味になります。それがインスタンスに属するため、結局インスタンス変数と呼ばれるのです。これは Smalltalk からの遺産です。Smalltalk では可視性の制限はありません。 全て メソッドは公開されています。したがって、インスタンス変数は のみ Smalltalk でカプセル化を行う方法、そして結局のところ、カプセル化は OO の柱の 1 つです。Rubyでは、 は (たとえば上で見たように) 可視性の制限があるため、その理由でインスタンス変数を非表示にすることは厳密には必要ありません。ただし、別の理由もあります。均一アクセス原則。
UAP には、次の方法が記載されています。 使用 機能はその機能がどのようなものであるかに依存しない必要があります 実装されました. 。したがって、機能へのアクセスは常に同じである必要があります。ユニフォーム。その理由は、機能の作成者は、機能のユーザーに影響を与えることなく、機能の内部動作を自由に変更できるためです。言い換えれば、それは基本的なモジュール性です。
これは、たとえば、サイズが変数に格納されるか、毎回動的に計算されるか、最初に遅延計算されて変数に格納されるか、メモ化されるかなどに関係なく、コレクションのサイズの取得は常に同じである必要があることを意味します。明白に聞こえますが、たとえば、Java はこれを誤解します。
obj.size # stored in a field
対
obj.getSize() # computed
ルビーは簡単な方法で逃げます。Rubyには、 1つ 機能の使用方法:メッセージを送信しています。方法が 1 つしかないため、アクセスは自明のことながら均一です。
長い話を簡単にまとめると、次のようになります。別のインスタンスのインスタンス変数にアクセスすることはできません。そのインスタンスと対話できるのはメッセージ送信経由のみです。つまり、他のオブジェクトはメソッド (この場合は少なくとも protected
可視性)を使用してインスタンス変数にアクセスするか、リフレクションを使用してそのオブジェクトのカプセル化に違反する必要があります(したがって、均一アクセスが失われ、結合が増加し、将来の破損の危険があります)。 instance_variable_get
).
その栄光のすべてがここにあります。
#!/usr/bin/env ruby
class X
def initialize(a=1)
@a = a
end
def m(other)
@a == other.a
end
protected
attr_reader :a
end
require 'test/unit'
class TestX < Test::Unit::TestCase
def test_that_m_evaluates_to_true_when_passed_two_empty_xs
x, y = X.new, X.new
assert x.m(y)
end
def test_that_m_evaluates_to_true_when_passed_two_xs_with_equal_attributes
assert X.new('foo').m(X.new('foo'))
end
end
または、次のようにすることもできます。
class X
def m(other)
@a == other.instance_variable_get(:@a)
end
end
この 2 つのうちのどちらを選択するかは個人の好みの問題だと思います。の Set
標準ライブラリのクラスはリフレクションバージョンを使用しますが、 それ 用途 instance_eval
その代わり:
class X
def m(other)
@a == other.instance_eval { @a }
end
end
(理由は分かりません。多分 instance_variable_get
当時は単に存在しなかった Set
書かれた。Ruby は 2 月で 17 歳になります。stdlib の一部は非常に初期の頃のものです。)
あなたはinstance_eval
オプションを使用(@jleedevが掲載される)、およびgetter
メソッドを使用しない場合、あなたはまだそれがprotected
維持することができます。
protected
方法をしたい場合は、、ちょうど、同じクラスのオブジェクトから読み取ることができゲッターを作成するには、次の操作を行います:
class X
def new()
@a = 1
end
def m( other )
@a == other.a
end
protected
def a
@a
end
end
x = X.new()
y = X.new()
x.m( y ) # Returns true
x.a # Throws error
わからないが、これは役立つかもしれません。
クラスの外では、それは少し難しくなります。
# Doesn't work:
irb -> a.@foo
SyntaxError: compile error
(irb):9: syntax error, unexpected tIVAR
from (irb):9
# But you can access it this way:
irb -> a.instance_variable_get(:@foo)
=> []
http://whynotwiki.com/Ruby_/_Variables_and_constants#Variable_scope.2Faccessibilityする