一般に、継承やMix-inを使わずにメソッドを上書きするのは推奨されることではありません。
読み手にとって何が起こっているかわからないコードは、バグの温床となるからです。
しかし、上書きができないというわけではありません。そう、Rubyならね…。
このエントリでは、特異メソッドを定義してインスタンスメソッドを上書きする方法を紹介します。
「特異メソッド」という名前は、特異という言葉が難しく少しわかりづらいですが、英語を見るとわかりやすいです。
特異メソッドは、英語では「instance-specific method」と言います。
instance-specific、つまりインスタンス固有のメソッドと理解できます。
Rubyでは、クラスメソッドは特異メソッドと説明されることがあります。
このことも、特異メソッドはインスタンス(=オブジェクト)固有のメソッドであるという前提に立つと、すっきり理解することができます。
Rubyでは、例えばclass Post
のように定義したクラスは、Class
クラスのオブジェクトになります。
クラスメソッドはクラスオブジェクト固有のメソッドになることから、特異メソッドと言えるわけですね。
インスタンスメソッドを上書きするには、インスタンスに対して特異メソッドを定義すれば良いということがわかりました。
特異メソッドを定義する方法はいくつかありますが、まずはinstance_exec
を使う方法から紹介します。
下記のコードをご覧ください。
post1
インスタンスに対してinstance_exec
を呼び出しています。
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_exec
ブロック内のself
はレシーバのオブジェクトpost1
を指すので、ブロック内のdef
ではpost1
オブジェクト固有のメソッド(特異メソッド)を定義していることになります。
post1
オブジェクト固有なので、post2
オブジェクトには影響はありません。
post1
のcalc_likes
だけが、置き換えた値:replacer
を返すようになります。
instance_exec
の引数は、ブロック引数としてブロック内に渡すことができます。
ここでは任意の値:replacer
を渡し、ブロック引数like_count
として受け取っています。
いったんインスタンス変数@_like_count
へ代入しているのは、self.calc_likes
メソッドから参照できるようにするためです。
実は、特異メソッドの定義は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
とてもシンプルに書くことができました。
以上です。
このエントリでは、特異メソッドを定義してインスタンスメソッドを上書きする方法を紹介しました。
コメントを送る
コメントはブログオーナーのみ閲覧できます