あなたの当面の問題に対するいくつかの良い答えがすでにありますが、コメントを必要とするあなたのコードの他の部分に気付きました. (しかし、それらのほとんどは些細なことです。)
ここに 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 つしかありません。メッセージを送信することです。一方向しかないので、アクセスは簡単に均一です。
つまり、簡単に言うと、別のインスタンスのインスタンス変数にアクセスすることはできません。メッセージ送信を介してのみそのインスタンスと対話できます。つまり、他のオブジェクトがprotected
そのインスタンス変数にアクセスするためのメソッド (この場合は少なくとも可視性) を提供するか、そのオブジェクトのカプセル化に違反する必要があります (したがって、Uniform Access が失われ、カップリングが増加し、将来の破損のリスクがあります)。 ) リフレクションを使用する (この場合は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
どちらを選ぶかは、個人の好みの問題だと思います。標準ライブラリのSet
クラスはリフレクション バージョンを使用しますが、代わりinstance_eval
に以下を使用します。
class X
def m(other)
@a == other.instance_eval { @a }
end
end
(理由はわかりません。書かれinstance_variable_get
たときには単に存在していなかったのかもしれませんSet
。Ruby は 2 月に 17 歳になる予定です。stdlib の一部は非常に初期のものです。)