LoginSignup
31
27

More than 5 years have passed since last update.

既存メソッドのオーバーライド

Last updated at Posted at 2015-02-18

Rubyで既存メソッドを拡張したい。拡張性も持たせたい。そんな時のやり方メモ。
例として Array#to_sの拡張をする。

継承クラスでのメソッドオーバーライド

Arrayの継承クラスとsuperを使う。Arrayが使われている他の部分を壊さないので安全。
Array#to_sのように、本来なら引数を取らないメソッドを引数ありでオーバーライドする場合は、super()と明示的に引数なしで呼ぶ必要がある。

class OreArray < Array 
  def to_s(args)
    if args == :hello
      "extend!!!"
    else
      super()
    end
  end
end

a = OreArray.new([1,2,3])
p a.to_s         #=> "[1, 2, 3]"
p a.to_s(:hello) #=> "extend!!!"

クラスそのものへのモンキーパッチ

Array#to_sそのものの動きを変更したい場合。
aliasalias_method を利用する。

class Array
  alias_method :__to_s__, :to_s

  # 拡張子したto_s
  def to_s
    'extend!!!!'
  end

  # 確認用
  def to_s_origin
    __to_s__
  end

  # 外部からアクセスできないようにする
  private :__to_s__
end

a = [1,2,3]
p a.to_s        #=> "extend!!!!"
p a.to_s_origin #=> "[1, 2, 3]"
p a.__to_s__    #=> `<main>': private method `__to_s__' called for [1, 2, 3]:Array (NoMethodError)

拡張性のある拡張をする

send を使うと、モンキーパッチが当てやすくなる。
メソッドの命名規則を強要するのがメリットでもありデメリットでもある。

# Arrayの拡張
class Array
  alias_method :__to_s__, :to_s
  private :__to_s__

  # 拡張子したto_s
  def to_s(arg=nil)
    if arg #arg.is_a?(Symbol) とするほうが明示的でいいかも?
      send("to_s_#{arg}")
    else
      __to_s__
    end
  end

  private

  def to_s_join
    join('-')
  end
end


# Arrayの拡張の拡張
class Array
  private

  def to_s_reverse
    reverse.to_s
  end
end

a = [1, 2, 3]
p a.to_s            #=> "[1, 2, 3]"
p a.to_s(:join)     #=> "1-2-3"
p a.to_s(:reverse)  #=> "[3, 2, 1]"
p a.to_s(:hogegege) #=> undefined method `to_s_hogegege' for [1, 2, 3]:Array (NoMethodError)

拡張性はないけどまとまりのある拡張

一度定義したら別にもう拡張しないし、と言う場合。
一箇所に処理がまとまって、読みやすい。Rails の Date#to_sはこのような拡張をしている。

class Array
  alias_method :__to_s__, :to_s
  private :__to_s__

  TO_STRING_EXTEND = {
    join: ->(arr){ arr.join('-') },
    reverse: ->(arr){ arr.reverse.to_s },
  }

  # 拡張子したto_s
  def to_s(arg=nil)
    if method = ::Array::TO_STRING_EXTEND[arg]
      method.call(self)
    else
      __to_s__
    end
  end
end


a = [1, 2, 3]
p a.to_s            #=> "[1, 2, 3]"
p a.to_s(:join)     #=> "1-2-3"
p a.to_s(:reverse)  #=> "[3, 2, 1]"
p a.to_s(:hogegege) #=> "[1, 2, 3]" / to_sと同じ
31
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
27