クラスのメソッドにモンキーパッチを適用しているとしましょう。オーバーライドされたメソッドからオーバーライドされたメソッドを呼び出すにはどうすればよいですか?つまり、少し似ています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
ます。これは、依存性注入デザインパターン、ファクトリメソッドデザインパターン、抽象ファクトリデザインパターンなどを使用する場合にさらに効果的です。その場合、変更する必要があるのは場所だけだからです。
オブジェクトの作成を制御しない場合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
オブジェクトがコードに組み込まれるシステムの境界で、オブジェクトを別のオブジェクトにラップし、コード内の他の場所で元のオブジェクトの代わりにそのオブジェクトを使用します。
これは、stdlibObject#DelegateClass
のライブラリのヘルパーメソッドを使用します。delegate
Module#prepend
:Mixin Prepending上記の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
代わりにミックスインをprepend
します:
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(およびほとんどすべての単一ディスパッチベースのオブジェクト指向言語)では、メソッドは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
キーワードを(乱用)使用できますか?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
beforeコンビネータは、メソッドの最後prepend
で呼び出すオーバーライドメソッドを使用してミックスインを実行するのと同じです。同様に、afterコンビネータは、メソッドの最初で呼び出すオーバーライドメソッドを使用してミックスインを実行するのと同じです。super
prepend
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
この提案のように動作します。
エイリアシングメソッドを見てください。これは、メソッドの名前を新しい名前に変更するようなものです。
詳細と開始点については、この置換方法の記事(特に最初の部分)を参照してください。Ruby APIのドキュメントには、(それほど複雑ではない)例も記載されています。
オーバーライドを行うクラスは、元のメソッドを含むクラスの後に再ロードする必要があるため、オーバーライドを行うrequire
ファイル内にあります。