[解決済み] インスタンスメソッドをモンキーパッチする場合、新しい実装からオーバーライドされたメソッドを呼び出すことは可能ですか?
質問
クラス内のメソッドをモンキーパッチする場合、オーバーライドされたメソッドからオーバーライドされたメソッドを呼び出すにはどうすればよいでしょうか?すなわち、次のようなものです。
super
例
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
解決方法は?
EDIT : この回答を最初に書いてから9年が経ちましたので、最新の内容にするために美容整形手術を受ける価値があります。
編集前のバージョンはこちらでご覧いただけます。 こちら .
を呼び出すことはできません。 上書きされた メソッドを名前かキーワードで指定します。これは、モンキーパッチが避けられ、代わりに継承が好まれる多くの理由のうちの一つです。 できる を呼び出します。 オーバーライド メソッドを使用します。
モンキーパッチを回避する
継承
だから、なるべくなら、こんな感じのものがいい。
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
オブジェクトがコードに入ってきたら、それを別のオブジェクトにラップし、さらに
その
オブジェクトの代わりに、コード内の他のすべての場所で元のオブジェクトを使用します。
これは
Object#DelegateClass
のヘルパーメソッドです。
delegate
のライブラリーをstdlibに追加しました。
"クリーン "なモンキーパッチング
Module#prepend
: ミキシンのプリペンド
上記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モジュールのprependとderivation
ミキシンの継承(壊)
このようなことを試している人を見たことがあります (そして StackOverflow でなぜうまくいかないのかを質問しています)。
include
の代わりにmixinを使用します。
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(およびほとんどすべてのシングルディスパッチベースのOO言語)では、メソッドは特定のレシーバオブジェクトにバインドされます。
self
をRubyで作成しました。言い換えれば、メソッドは常に、それがどのオブジェクトに対して呼び出されたかを知っています。
self
があります。しかしクラスから直接メソッドを取得した場合、その
self
は?
さて、そうではないのですが、そのために必要なのが
bind
我々の
UnboundMethod
をオブジェクトに変換し、そのオブジェクトが
Method
オブジェクトを呼び出すことができます。(
UnboundMethod
を知らないと何をすればいいのかわからないので、呼び出すことはできません。
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!
Refinementsを使ったより洗練された例を、この質問で見ることができます。 特定のメソッドに対してモンキーパッチを有効にするには?
断念したアイデア
Rubyコミュニティが
Module#prepend
には、複数の異なるアイデアがあり、古い議論でも参照されることがあります。これらはすべて
Module#prepend
.
メソッドコンビネータ
ひとつは、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
交換
ビフォアコンビネーターは、次のものと同等です。
prepend
を呼び出すオーバーライドメソッドを持つミキシンを作成します。
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 mixin は、基本的に
old
を提案します。
redef
キーワード
上記と似ていますが、新しいキーワードを追加する代わりに
呼び出し
を残し、上書きされたメソッドを
def
を単独で使用する場合、新しいキーワードを
再定義
メソッドになります。これは後方互換性があり、現在の構文はいずれにせよ違法だからです。
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
を追加する代わりに
に
の意味を再定義することもできます。
super
内側
redef
:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
交換
redef
の中でメソッドをオーバーライドするのと同じです。
prepend
というミキシンがあります。
super
のような挙動をします。
super
または
old
を提案します。
関連
-
[解決済み] RubyのNameError
-
[解決済み] Ruby、スタックレベルが深すぎる (SystemStackError)
-
[解決済み] Rubyがブロックの中でパイプ文字を使うことについて、誰か説明してください。
-
[解決済み] 配列をアルファベット順に並べるには?
-
[解決済み] nil から String への暗黙の変換ができないエラー
-
[解決済み] rubyでto_yamlに書式オプションを指定することはできますか?
-
[解決済み] 変数が整数であるかどうかのチェック
-
[解決済み] 配列からランダムに選択する方法は?
-
[解決済み] ルビーです。インスタンスからクラスメソッドを呼び出す
-
[解決済み】Rubyモジュールをインクルードせずに、そのインスタンスメソッドを呼び出すことはできますか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] ルビー階乗関数
-
[解決済み] 配列をアルファベット順に並べるには?
-
[解決済み] あなたが提供した認証メカニズムはサポートされていません。AWS4-HMAC-SHA256を使用してください。
-
[解決済み] rubyでディレクトリからすべてのファイルを要求する最良の方法?
-
[解決済み] Gem::Specification.reset中に未解決のスペックがある。
-
[解決済み] Rubyでリフレクション?
-
[解決済み] Rubyでファイルを移動するにはどうしたらいいですか?
-
ERRORの問題を解決します。rails のインストール時に gem ネイティブ拡張のビルドに失敗しました。
-
[解決済み] define_methodに引数を渡すには?
-
[解決済み] Railsです。railsでhas_oneアソシエーションを使ったbuildの使い方