クラスのメソッドにサルパッチを適用しているとしましょう。オーバーライドされたメソッドからオーバーライドされたメソッドを呼び出すにはどうすればよいですか?つまりsuper
のようなもの
例えば。
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
EDIT:私が最初にこの答えを書いてから9年が経ちましたが、最新の状態に保つためには美容整形手術に値します。
編集前の最後のバージョンを見ることができます here 。
名前またはキーワードでoverwrittenメソッドを呼び出すことはできません。これは、明らかにcanoverriddenメソッドを呼び出すため、モンキーパッチを回避し、代わりに継承を優先する多くの理由の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 など) 、次に Wrapper Design Pattern を使用できます。
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
オブジェクトがコードに含まれるシステムの境界で、それを別のオブジェクトにラップし、コード内の元々のオブジェクトの代わりにthatオブジェクトを使用します。
これは、stdlibの delegate
ライブラリの Object#DelegateClass
ヘルパーメソッドを使用します。
Module#prepend
:Mixin Prepending上記の2つの方法では、サルのパッチを回避するためにシステムを変更する必要があります。このセクションでは、システムの変更がオプションではない場合の、サルのパッチングの優先的かつ最も侵襲性の低い方法を示します。
Module#prepend
は、このユースケースをほぼ正確にサポートするために追加されました。 Module#prepend
は、Module#include
と同じことを行いますが、mixinで直接ミックスする点が異なります下クラス:
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 module prepend vs derivation
私は何人かの人々がこれを試して(そして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
は呼び出されません(そしてwereが呼び出された場合、 super
は実際にはFoo#bar
を参照するのではなく、むしろObject#bar
が常に最初に見つかるため、存在しないFoo#bar
を参照します。
大きな問題は、実際に実際のメソッドを保持せずに、どのようにbar
メソッドを保持するかということです。答えは、関数型プログラミングによくあるように、あります。メソッドを実際のobjectとして保持し、クロージャー(つまりブロック)を使用して、and we onlyそのオブジェクトを保持していることを確認します。
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
は単なるローカル変数であるため、クラス本体の最後でスコープ外になり、どこからでもアクセスすることはできませんeven反射を使用して!そして Module#define_method
はブロックを取り、ブロックは周囲の字句環境を閉じます(これはwhyここではdef
の代わりにdefine_method
を使用しています)、it =(およびonly it)は、スコープ外になった後でも、old_bar
にアクセスできます。
簡単な説明:
old_bar = instance_method(:bar)
ここでは、bar
メソッドを UnboundMethod
メソッドオブジェクトにラップし、ローカル変数old_bar
に割り当てています。これは、上書きされた後でもbar
を保持する方法があることを意味します。
old_bar.bind(self)
これは少し注意が必要です。基本的に、Ruby(およびほぼすべてのシングルディスパッチベースのOO言語)では、メソッドはRubyでself
と呼ばれる特定のレシーバーオブジェクトにバインドされます。言い換えれば、メソッドは常にどのオブジェクトが呼び出されたかを知っており、self
が何であるかを知っています。しかし、クラスから直接メソッドを取得しましたが、そのself
が何であるかをどのように知るのでしょうか?
そうではありません。そのため、まずオブジェクトに bind
UnboundMethod
を指定する必要があります。このオブジェクトは、後で呼び出すことができる Method
オブジェクトを返します。 (UnboundMethod
sは、self
を知らなければ何をすべきかわからないため、呼び出すことができません。)
そして、bind
それに何をしますか?私たちは単にbind
を自分自身に振る舞います。そのように振る舞いますexactly元の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:after
内でbar
の戻り値にアクセスできるかどうか、およびどのようにアクセスするかは明確ではありません。 super
キーワードを(ab)使用できますか?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
Beforeコンビネータは、メソッドの非常にendでprepend
を呼び出すオーバーライドメソッドでミックスインをsuper
ingすることと同等です。同様に、afterコンビネーターは、メソッドの非常にbeginningでprepend
を呼び出すオーバーライドメソッドを使用して、ミックスインをsuper
ingすることと同等です。
また、super
を呼び出した後、andの前に何かを行うことができます。また、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
がoverriddenメソッドを呼び出すのと同じ方法でoverwrittenメソッドを呼び出すことができます。
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
これの主な問題は、後方互換性がないことです:old
と呼ばれるメソッドがある場合、それを呼び出すことができなくなります!
super
edミックスインのオーバーライドメソッドのprepend
は、この提案のold
と本質的に同じです。
redef
キーワード上記と同様ですが、上書きされるメソッドcallingに新しいキーワードを追加し、def
をそのままにする代わりに、redefiningメソッドに新しいキーワードを追加します。いずれにせよ、構文は現在不正であるため、これは後方互換性があります。
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
two新しいキーワードを追加する代わりに、super
内のredef
の意味を再定義することもできます。
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
メソッドをredef
iningすることは、prepend
edミックスインでメソッドをオーバーライドすることと同等です。オーバーライドメソッドのsuper
は、この提案のsuper
またはold
のように動作します。
エイリアスメソッドを見てください。これは、メソッドの名前を新しい名前に変更するようなものです。
詳細と開始点については、これをご覧ください メソッドの置き換え記事 (特に最初の部分)。 Ruby API docs も、(より複雑ではない)例を提供します。