クラスのメソッドにモンキーパッチを適用しているとしましょう。オーバーライドされたメソッドからオーバーライドされたメソッドを呼び出すにはどうすればよいですか?つまり、少し似ています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#barsuperFoo#barObject#barFoo#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_methoddefine_methoddefold_bar
簡単な説明:
old_bar = instance_method(:bar)
ここでは、barメソッドをUnboundMethodメソッドオブジェクトにラップし、ローカル変数に割り当てていますold_bar。barつまり、上書きされた後でも保持する方法があります。
old_bar.bind(self)
これは少し注意が必要です。基本的に、Ruby(およびほとんどすべての単一ディスパッチベースのオブジェクト指向言語)では、メソッドは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キーワードを(乱用)使用できますか?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
beforeコンビネータは、メソッドの最後prependで呼び出すオーバーライドメソッドを使用してミックスインを実行するのと同じです。同様に、afterコンビネータは、メソッドの最初で呼び出すオーバーライドメソッドを使用してミックスインを実行するのと同じです。superprependsuper
また、呼び出しの前後に処理を実行したりsuper、複数回呼び出したり、の戻り値をsuper取得および操作したりできるため、メソッドコンビネータよりも強力になります。superprepend
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'
redefprependメソッドをインすることは、 edミックスインでメソッドをオーバーライドすることと同じです。superオーバーライドメソッドでは、この提案のようにsuper、またはoldこの提案のように動作します。
エイリアシングメソッドを見てください。これは、メソッドの名前を新しい名前に変更するようなものです。
詳細と開始点については、この置換方法の記事(特に最初の部分)を参照してください。Ruby APIのドキュメントには、(それほど複雑ではない)例も記載されています。
オーバーライドを行うクラスは、元のメソッドを含むクラスの後に再ロードする必要があるため、オーバーライドを行うrequireファイル内にあります。