29

ルビーのクラスにモンキーパッチを適用する一般的な方法が2つあることに気づきました。

次のように、クラスの新しいメンバーを定義します。

class Array
   def new_method
     #do stuff
   end
end

そして、クラスオブジェクトでclass_evalを呼び出します。

Array.class_eval do
   def new_method
      #do stuff
   end
end

2つの間に違いがあるかどうか、そして一方のアプローチを他方よりも使用することに利点があるかどうか疑問に思っていますか?

4

2 に答える 2

59

正直なところ、私はより自然に感じるため、最初のフォーム(クラスを再開する)を使用していましたが、あなたの質問により、この件について調査する必要がありました。結果は次のとおりです。

クラスを再開する際の問題は、再開しようとしていた元のクラスが何らかの理由でその時点で定義されていない場合、新しいクラスを暗黙のうちに定義することです。結果は異なる場合があります。

  1. メソッドをオーバーライドせずに新しいメソッドを追加するだけで、元の実装が定義されている場合 (たとえば、クラスが最初に定義されたファイルが読み込まれる場合)、後ですべて問題ありません。

  2. いくつかのメソッドを再定義し、元のメソッドが後でロードされると、メソッドは元のバージョンで上書きされます。

  3. 最も興味深いケースは、標準のオートロードまたは (Rails で使用されているような) ファンシーなリロード メカニズムを使用してクラスをロード/リロードする場合です。これらのソリューションの一部は、未定義の定数を参照するときに呼び出されるconst_missingに依存しています。その場合、オートローディング メカニズムは、未定義のクラスの定義を見つけてロードしようとします。ただし、独自にクラスを定義している場合 (既に定義されているクラスを再度開くつもりである場合) は、もはや「欠落」することはなく、オートロード メカニズムがトリガーされないため、元のクラスがまったくロードされない可能性があります。

一方、使用class_evalすると、その時点でクラスが定義されていない場合に即座に通知されます。さらに、class_evalメソッドを呼び出すときにクラスを参照しているため、オートロード メカニズムはクラスの定義を見つけてロードする可能性があります。

それを念頭に置いclass_evalて、より良いアプローチのようです。とはいえ、別の意見もいただけるとうれしいです。

于 2012-04-26T18:33:46.450 に答える
10

範囲

KL-7 が指摘しなかった大きな違いの 1 つは、新しいコードが解釈される範囲です。

クラスを操作するためにクラスを (再) 開いている場合、追加した新しいコードは (元の) クラスのスコープで解釈されます。Module#class_eval
を 使用してクラスを操作している場合、追加する新しいコードは #class_eval への呼び出しを囲むスコープで解釈され、class-scope を認識しません。知らないと、この動作は直感に反し、デバッグが困難なエラーにつながる可能性があります。

CONSTANT    = 'surrounding scope'

# original class definition (uses class scope)
class C
  CONSTANT  = 'class scope'

  def fun()  p CONSTANT  end
end
C.new.fun    # prints: "class scope"


# monkey-patching with #class_eval: uses surrounding scope!
C.class_eval do
  def fun()  p CONSTANT  end
end
C.new.fun    # prints: "surrounding scope"


# monkey-patching by re-opening the class: uses scope of class C
class C
  def fun()  p CONSTANT  end
end
C.new.fun    # prints: "class scope"
于 2016-01-13T02:09:43.363 に答える