478

クラスのメソッドにモンキーパッチを適用しているとしましょう。オーバーライドされたメソッドからオーバーライドされたメソッドを呼び出すにはどうすればよいですか?つまり、少し似ていますsuper

例えば

class Foo
  def bar()
    "Hello"
  end
end 

class Foo
  def bar()
    super() + " World"
  end
end

>> Foo.new.bar == "Hello World"
4

3 に答える 3

1229

編集:私が最初にこの答えを書いてから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、たとえば、オブジェクトが制御外のフレームワーク(たとえば、 ラッパーデザインパターンを使用できます。

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モジュールの付加と派生

Mixinの継承(壊れた)

私は何人かの人々がこのようなことを試みている(そしてなぜそれが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_barbarつまり、上書きされた後でも保持する方法があります。

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この提案のように動作します。

于 2010-12-17T14:08:43.027 に答える
13

エイリアシングメソッドを見てください。これは、メソッドの名前を新しい名前に変更するようなものです。

詳細と開始点については、この置換方法の記事(特に最初の部分)を参照してください。Ruby APIのドキュメントには、(それほど複雑ではない)例も記載されています。

于 2010-12-17T11:52:50.403 に答える
-2

オーバーライドを行うクラスは、元のメソッドを含むクラスの後に再ロードする必要があるため、オーバーライドを行うrequireファイル内にあります。

于 2016-04-19T04:39:53.333 に答える