環境
- Ruby 2.6
やりたいこと
使ってるGemの挙動を一部変えたくて、インスタンスメソッドを上書きしたい できれば黒魔術にならずにシンプルに変えたい
コード例ではCatクラスがあって、本来はhello()
で「nyaa」を返すが「bowwow」に変更したい
うまくいかない例
- 普通にmoduleを作成してincludeする
- Rubyのクラス継承チェインの上位に上書きしたいmoduleが来てしまうため無理
# NOT WORKING class Cat def hello "nyaa" end end module ExtendCat def hello "bowwow" end end class Cat include ExtendCat end cat = Cat.new puts cat.hello
方法1 クラス継承
- 1番手っ取り早い、クラスを継承してメソッドを上書き
- 王道中の王道だが、rubyは多段継承を許してないので多用できない
class Cat def hello "nyaa" end end class MyCat < Cat def hello "#{super} > bowwow" # スーパークラスのメソッドはsuperで呼びさせるが、 # 同じ引数が渡されることに注意 何も渡したくない場合はsuper() end end cat = MyCat.new puts cat.hello
方法2 alias_method
- 既存とは別の名前でメソッドを定義して、エイリアス機能を使ってメソッドをすり替えるモンキーパッチらしい方法
- デメリットとして、エイリアスが被る可能性がある、新しいメソッドから古いメソッドを自動で呼べない
class Cat def hello "nyaa" end end module ExtendCat def new_hello "#{old_hello} > bowwow" end end class Cat include ExtendCat alias_method :old_hello, :hello alias_method :hello, :new_hello end cat = Cat.new puts cat.hello
方法3 module#prepend
- モジュール内に変更したいメソッドを再定義し、クラスを再オープンしてモジュールを
prepend()
する - うまくいかない例の
include()
とは違い、モジュールで定義した方が優先されるため可能 - 方法2のやり方よりこっちのほうがよい
class Cat def hello "nyaa" end end module ExtendCat def hello "#{super} > bowwow" end end class Cat prepend ExtendCat end cat = Cat.new puts cat.hello
方法4 委譲
- DelegateClassを使うと、クラスを引数として受け取りそのクラスのオブジェクトにインスタンスメソッドを委譲する事が可能
- DelegateClass(Cat)でCatオブジェクトにインスタンスメソッドを委譲するクラスが定義され、そのクラスを継承したMyCatクラスを定義することでメソッド実行を委譲している
class Cat def hello "nyaa" end end class Mycat < DelegateClass(Cat) def initialize(cat) super end def hello "#{super} > bowwow" end end cat = Mycat.new(Cat.new) puts cat.hello
方法5 define_singleton_method
- そもそも特異メソッドとは、インスタンス固有のメソッドのこと(instance specific method)
define_singleton_method()
を使うと特異メソッドを定義できるためメソッドの挙動を書き換えることができる- 他の方法と違って挙動を戻すことが可能
class Cat def hello "nyaa" end end cat = Cat.new puts cat.hello orig = cat.method(:hello) # 元のメソッドを呼びたい時必要 cat.define_singleton_method(:hello) do "#{orig.call} > bowwow" end puts cat.hello # 元に戻す cat.singleton_class.send(:remove_method, :hello) puts cat.hello
方法6 refinements
- refinementsを使うと既存のメソッドの中身を一時的に変更できる
class Cat def hello "nyaa" end end module ExtendCat refine Cat do def hello "#{super} > bowwow" end end end using ExtendCat puts Cat.new.hello
方法7 instance_eval
- instance_evalを使うとメソッドやインスタンス変数を拡張できる。当然上書きもできるってわけ
class Cat def hello "nyaa" end end cat = Cat.new cat.instance_eval do def hello "#{super} > bowwow" end end puts cat.hello