クラスのメソッドにサルのパッチを適用しているとしましょう。オーバーライドするメソッドからオーバーライドされるメソッドを呼び出すにはどうすればよいですか?すなわち少し何か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_method
def
old_bar
簡単な説明:
old_bar = instance_method(:bar)
ここでは、bar
メソッドをUnboundMethod
メソッドオブジェクトにラップし、それをローカル変数に割り当てていますold_bar
。これは、bar
上書きされた後も保持する方法があることを意味します。
old_bar.bind(self)
これは少しトリッキーです。基本的に、Ruby(およびほとんどすべての単一ディスパッチベースのOO言語)では、self
Ruby で呼び出される特定のレシーバーオブジェクトにメソッドがバインドされます。言い換えると、メソッドは、呼び出されたオブジェクトを常に認識しており、それが何であるかを認識していself
ます。しかし、メソッドをクラスから直接取得したのself
ですが、それがどういうものかをどうやって知るのでしょうか?
まあ、それは私たちが必要とする理由である、ないbind
私たちをUnboundMethod
返します。最初のオブジェクト、にMethod
私たちは、その後呼び出すことができるというオブジェクトを。(UnboundMethod
sを呼び出せません。なぜなら、彼らは自分のことを知らなければ何をすべきかわからないからです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
、それを呼び出すことができなくなります!
super
prepend
edミックスインのオーバーライドメソッドの場合old
、この提案と基本的に同じです。
redef
キーワード上記と同様ですが、上書きされたメソッドを呼び出すための新しいキーワードdef
を追加してそのままにするのではなく、メソッドを再定義するための新しいキーワードを追加します。構文は現在いずれにせよ違法であるため、これは下位互換性があります。
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
2つの新しいキーワードを追加する代わりに、super
inside の意味を再定義することもできますredef
。
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
redef
メソッドの初期化は、prepend
edミックスインでメソッドをオーバーライドすることと同じです。super
以下のようなオーバーライドメソッドの振る舞いにおけるsuper
またはold
この提案インチ
bind
が同じold_method
変数を呼び出すとどうなりますか?
UnboundMethod#bind
は新しい別のを返すMethod
ため、異なるスレッドから2回続けて呼び出すか、2回同時に呼び出すかに関わらず、競合は発生しません。
old
とredef
?私の2.0.0にはそれらがありません。ああ、それは逃さない難しいたルビーにそれをしなかった他の競合のアイデアを:
エイリアスメソッドを見てみましょう。これは、メソッドの名前を新しい名前に変更するようなものです。
詳細と開始点については、こちらをご覧ください 置換方法に関する記事(特に最初の部分)を。RubyのAPIドキュメントは、また、(あまり凝っ)の例を提供します。