アルパカログ

カスタマーサポート (CS) とエンジニアリングを掛け算したい CRE (Customer Reliability Engineer) が気になる技術や思ったことなど。

【Ruby】特異メソッドを使ってインスタンスメソッドを再定義する

今日は Ruby の特殊な使い方、特異メソッドを使ってインスタンスメソッドを書き換えてしまう方法を紹介したいと思います。

黒魔術的になってしまうので推奨されませんが、こんな風にすればできるよということで、自己責任でお使いくださいね。

まずは下記のコードをご覧ください。もっとシンプルに書けますが、それは後ほどご紹介します。

class Post                                         
  def rank                                         
    calc_likes                                     
  end                                              

  def calc_likes                                   
    # some heavy calc                              
    :raw                                           
  end                                              
end                                                

post1 = Post.new                                   
post2 = Post.new                                   

post1.instance_exec(:replacer) do |like_count|     
  @_like_count = like_count                        

  def self.calc_likes                              
    @_like_count                                   
  end                                              
end

puts post1.rank # => replacer
puts post2.rank # => raw

個人的に特異メソッドという名前は特異という言葉が難しいことから少しわかりづらいなぁと思うのですが、これは英語名を見るとわかりやすいです。

特異メソッドは英語で instance-specific method と言って、インスタンス固有のメソッドと理解できます。

instance_exec ブロック内の self はレシーバのオブジェクト post1 を指すので、ブロック内の def では post1 オブジェクト固有のメソッド(特異メソッド)を定義していることになります。

post1 オブジェクト固有なので、post2 オブジェクトにはなんら影響がありません。post1 の calc_likes だけが任意に置き換えた値 :replacer を返すようになります。

instance_exec の引数はブロック引数としてブロック内に渡すことができます。

ここでは任意の値 :replacer を渡し、ブロック引数 like_count として受け取っています。

一度インスタンス変数へ代入しているのは、ブロック内で再定義したインスタンス self.calc_likes から参照できるようにするためです。

クラスメソッド=特異メソッドを理解する

ブロック内の def self.calc_likes をご覧ください。ここだけ見るとクラスメソッド定義のように見えます。

クラスメソッドが特異メソッドと説明されることも、オブジェクト固有のメソッドという考え方を持つとすっきり理解することができます。

Ruby においてはクラスも Class クラスのオブジェクト(インスタンス)なので、Post クラスも Class というクラスのオブジェクトです。

つまり、クラスメソッドはクラスオブジェクト固有の (instance-specific な) メソッドということから、特異メソッドと言えるわけです。

最後に教えてもらったシンプルな例、define_singleton_method を使った書き方をご紹介します。

class Post
  def rank
    calc_likes
  end

  def calc_likes
    # some heavy calc
    :raw
  end
end

post1 = Post.new
post2 = Post.new

post1.define_singleton_method(:calc_likes) do
  :replacer
end

puts post1.rank # => replacer
puts post2.rank # => raw

こちらも合わせて読みたい

alpacat.hatenablog.com