クラスのメソッドにサルのパッチを適用しているとしましょう。オーバーライドするメソッドからオーバーライドされるメソッドを呼び出すにはどうすればよいですか?すなわち少し何かsuper
例えば
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
クラスのメソッドにサルのパッチを適用しているとしましょう。オーバーライドするメソッドからオーバーライドされるメソッドを呼び出すにはどうすればよいですか?すなわち少し何かsuper
例えば
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
回答:
編集:私が最初にこの回答を書いてから9年が経過しました、そしてそれを最新に保つためにいくつかの美容整形に値します。
編集前の最新バージョンはこちらで確認できます。
上書きされたメソッドを名前またはキーワードで呼び出すことはできません。これは、オーバーライドされたメソッドを呼び出すことができるので、サルのパッチを回避し、代わりに継承を優先すべき多くの理由の1つです。
したがって、可能であれば、次のようなものを選択する必要があります。
class Foo
def bar
'Hello'
end
end
class ExtendedFoo < Foo
def bar
super + ' World'
end
end
ExtendedFoo.new.bar # => 'Hello World'
これは、Fooオブジェクトの作成を制御する場合に機能します。を作成するすべての場所を変更して、Foo代わりにを作成しExtendedFooます。Dependency Injection Design Pattern、Factory Method Design Pattern、Abstract Factory Design Patternを使用すると、これはさらにうまく機能しますまたはそれらの線に沿ったものその場合、変更する必要がある場所だけがあるためです。
の作成を制御しない場合、Fooオブジェクトのたとえばオブジェクトが制御外のフレームワークによって作成された場合など)ruby-on-railsたとえば)、次にラッパーデザインパターンを使用できます。
require 'delegate'
class Foo
def bar
'Hello'
end
end
class WrappedFoo < DelegateClass(Foo)
def initialize(wrapped_foo)
super
end
def bar
super + ' World'
end
end
foo = Foo.new # this is not actually in your code, it comes from somewhere else
wrapped_foo = WrappedFoo.new(foo) # this is under your control
wrapped_foo.bar # => 'Hello World'
基本的に、Fooオブジェクトがコードに入るシステムの境界で、オブジェクトを別のオブジェクトにラップし、コード内の他のすべての場所で元のオブジェクトの代わりにそのオブジェクトを使用します。
これは、stdlibのライブラリObject#DelegateClassからヘルパーメソッドを使用しdelegateます。
Module#prepend:Mixin追加上記の2つの方法では、サルのパッチを回避するためにシステムを変更する必要があります。このセクションでは、システムを変更することが選択肢ではない場合に、サルのパッチを適用するための推奨される最も侵襲性の低い方法を示します。
Module#prepend多かれ少なかれ正確にこのユースケースをサポートするために追加されました。クラスのすぐ下のミックスインでミックスすることを除いてModule#prepend、と同じことModule#includeをします:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
prepend FooExtensions
end
Foo.new.bar # => 'Hello World'
注:Module#prependこの質問についても少し書きました:Rubyモジュールの先頭に追加するか派生するか
私はつまり、何人かの人々がしよう(と、それはStackOverflowの上ここでは動作しない理由について尋ねる)このようなものを見てきましたincludeミックスインをINGの代わりにprependそれをINGの。
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
残念ながら、それはうまくいきません。継承を使用するため、これは良いアイデアですsuper。つまり、を使用できます。しかし、Module#includeミックスインを挿入上記手段継承階層内のクラス、FooExtensions#bar呼び出されることはありません(それがあればして呼び出され、super実際にはを参照していないだろうFoo#barけど、むしろにObject#barあるため、存在しない)をFoo#bar常に最初に発見されます。
大きな問題はbar、実際のメソッドを維持せずに、メソッドをどのように維持できるかということです。答えは、よくあるように、関数型プログラミングにあります。メソッドの保持を実際のオブジェクトとして取得し、クロージャー(つまりブロック)を使用して、そのオブジェクトのみを保持するようにします。
class Foo
def bar
'Hello'
end
end
class Foo
old_bar = instance_method(:bar)
define_method(:bar) do
old_bar.bind(self).() + ' World'
end
end
Foo.new.bar # => 'Hello World'
これは非常にクリーンです。これold_barは単なるローカル変数であるため、クラス本体の最後でスコープ外になり、リフレクションを使用しても、どこからでもアクセスすることはできません。そして、Module#define_methodはブロックを取り、ブロックは周囲の字句環境を閉じます(これが、ここではなく使用している理由です)。それが(そしてそれだけ)は、スコープから外れた後でも、にアクセスできます。define_methoddefold_bar
簡単な説明:
old_bar = instance_method(:bar)
ここでは、barメソッドをUnboundMethodメソッドオブジェクトにラップし、それをローカル変数に割り当てていますold_bar。これは、bar上書きされた後も保持する方法があることを意味します。
old_bar.bind(self)
これは少しトリッキーです。基本的に、Ruby(およびほとんどすべての単一ディスパッチベースのOO言語)では、selfRuby で呼び出される特定のレシーバーオブジェクトにメソッドがバインドされます。言い換えると、メソッドは、呼び出されたオブジェクトを常に認識しており、それが何であるかを認識していselfます。しかし、メソッドをクラスから直接取得したのselfですが、それがどういうものかをどうやって知るのでしょうか?
まあ、それは私たちが必要とする理由である、ないbind私たちをUnboundMethod返します。最初のオブジェクト、にMethod私たちは、その後呼び出すことができるというオブジェクトを。(UnboundMethodsを呼び出せません。なぜなら、彼らは自分のことを知らなければ何をすべきかわからないからですself。)
そして私たちはbindそれをどうするのですか?私たちはbindそれを自分自身に単純に伝えます。そうすれば、オリジナルとまったく同じように動作しbarます。
最後に、Methodから返されるを呼び出す必要がありますbind。Ruby 1.9では、そのための気の利いた新しい構文(.())がありますが、1.8を使用している場合は、このcallメソッドを使用するだけで済みます。それ.()がとにかく翻訳されるものです。
他のいくつかの質問を次に示します。これらの概念の一部が説明されています。
alias_method 鎖モンキーパッチで発生している問題は、メソッドを上書きするとメソッドが失われるため、それを呼び出すことができなくなることです。それでは、バックアップコピーを作成してみましょう!
class Foo
def bar
'Hello'
end
end
class Foo
alias_method :old_bar, :bar
def bar
old_bar + ' World'
end
end
Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'
この問題は、不要なold_barメソッドで名前空間を汚染していることです。このメソッドは、ドキュメントに表示され、IDEのコード補完に表示され、リフレクション中に表示されます。また、それを呼び出すこともできますが、そもそもその動作が気に入らなかったため、おそらくサルにパッチを当てたため、他の人に呼び出されたくないかもしれません。
これには望ましくないプロパティがいくつかあるという事実にもかかわらず、残念ながらAciveSupportを通じて普及しましたModule#alias_method_chain。
システム全体ではなく、いくつかの特定の場所でのみ異なる動作が必要な場合は、絞り込みを使用して、モンキーパッチを特定のスコープに制限できます。Module#prepend上記の例を使用して、ここでそれを示します。
class Foo
def bar
'Hello'
end
end
module ExtendedFoo
module FooExtensions
def bar
super + ' World'
end
end
refine Foo do
prepend FooExtensions
end
end
Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!
using ExtendedFoo
# Activate our Refinement
Foo.new.bar # => 'Hello World'
# There it is!
この質問では、洗練を使用したより洗練された例を見ることができます:特定のメソッドでサルのパッチを有効にする方法は?
Rubyコミュニティがに落ち着く前はModule#prepend、古い議論で参照されていることが時折見られるかもしれない、さまざまなアイデアが浮かんでいました。これらはすべてに含まれModule#prependます。
1つのアイデアは、CLOSのメソッドコンビネーターのアイデアでした。これは基本的に、アスペクト指向プログラミングのサブセットの非常に軽量なバージョンです。
のような構文を使用する
class Foo
def bar:before
# will always run before bar, when bar is called
end
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
barメソッドの実行を「フック」することができます。
ただし、bar内での戻り値にアクセスできるかどうか、またどのようにアクセスするかは明確ではありませんbar:after。多分私達はsuperキーワードを(ab)使用できますか?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
前コンビネータは、と等価であるprepend呼び出すオーバーライドメソッドとミックスインをINGのsuper非常に終了方法の。同様に、コンビネータ後と同等ですprepend呼び出すオーバーライドメソッドでミックスインをINGのsuper非常に始まる方法。
また、前のものを行うことができますし、呼び出した後super、あなたが呼び出すことができsuper、複数回、そして両方の検索および操作superすること、の戻り値をprependメソッドコンビネータよりも強力。
class Foo
def bar:before
# will always run before bar, when bar is called
end
end
# is the same as
module BarBefore
def bar
# will always run before bar, when bar is called
super
end
end
class Foo
prepend BarBefore
end
そして
class Foo
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
# is the same as
class BarAfter
def bar
original_return_value = super
# will always run after bar, when bar is called
# has access to and can change bar’s return value
end
end
class Foo
prepend BarAfter
end
old キーワードこのアイデアは、に類似した新しいキーワードを追加しsuperます。これにより、上書きされたメソッドを呼び出すのと同じ方法でsuper、上書きされたメソッドを呼び出すことができます。
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
これの主な問題は、下位互換性がないことです:と呼ばれるメソッドがある場合old、それを呼び出すことができなくなります!
superprependedミックスインのオーバーライドメソッドの場合old、この提案と基本的に同じです。
redef キーワード上記と同様ですが、上書きされたメソッドを呼び出すための新しいキーワードdefを追加してそのままにするのではなく、メソッドを再定義するための新しいキーワードを追加します。構文は現在いずれにせよ違法であるため、これは下位互換性があります。
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
2つの新しいキーワードを追加する代わりに、superinside の意味を再定義することもできますredef。
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
redefメソッドの初期化は、prependedミックスインでメソッドをオーバーライドすることと同じです。super以下のようなオーバーライドメソッドの振る舞いにおけるsuperまたはoldこの提案インチ
bindが同じold_method変数を呼び出すとどうなりますか?
UnboundMethod#bindは新しい別のを返すMethodため、異なるスレッドから2回続けて呼び出すか、2回同時に呼び出すかに関わらず、競合は発生しません。
oldとredef?私の2.0.0にはそれらがありません。ああ、それは逃さない難しいたルビーにそれをしなかった他の競合のアイデアを:
エイリアスメソッドを見てみましょう。これは、メソッドの名前を新しい名前に変更するようなものです。
詳細と開始点については、こちらをご覧ください 置換方法に関する記事(特に最初の部分)を。RubyのAPIドキュメントは、また、(あまり凝っ)の例を提供します。