読者です 読者をやめる 読者になる 読者になる

みんなのちからになりたい

コピペでブログラムつくっていきたい

標準で用意されてるクラスのinitializeをoverrideする

きのうのやつまだ解決してないんだけども、ああいうことができるならinitializeとかもoverrideできるのかもと思ってやってみる。
そもそも標準で用意されてるクラスにinitializeがあるのか調べてみる。

てっとり早くDateでやってみる。
ちなみにDate.methodsにはなかったので手当り次第やってみたら

[1] pry(main)> require "date"
=> true
[2] pry(main)> Date.private_methods.sort
=> [:Array,
 :Complex,
 ~
:included,
:inherited,
:initialize,
:initialize_copy,
:iterator?,
~

あった。

ということでoverrideしてみる。

require "date"
class Date
  def initialize(*args)
    p "kita!"
  end
  def aaa
    "aaa"
  end
end

p dd = Date.new(2013, 1, 31)
p dd.day
p dd.aaa


$ ruby buildin.rb
#<Date: 2013-01-31 ((2456324j,0s,0n),+0s,2299161j)>
31
"aaa"

ならない。
というかまったく通ってない。
newしてるからinitialize通ると思うんだけどなあと思っていろいろぐぐってみると

instance method Object#initialize

このメソッドは Class#new から新しく生成されたオブ ジェクトの初期化のために呼び出されます。他の言語のコンストラクタに相当します。 デフォルトの動作ではなにもしません。

initialize には Class#new に与えられた引数がそのまま渡されます。

サブクラスではこのメソッドを必要に応じて再定義されること が期待されています。

initialize という名前のメソッドは自動的に private に設定され ます。

なるほど、newか。
そしてprivate_methodsに引っかかったのも自動的にprivateになるからということらしい。

instance method Class#new

自身のインスタンスを生成して返します。 このメソッドの引数はブロック引数も含め Object#initialize に渡されます。

new は Class#allocate でインスタンスを生成し、 Object#initialize で初期化を行います。

とりあえずnewをoverrideしてみればいけそうな気がする。
newは特異メソッド、要はstaticなのでselfを付ける。

やってみる。

require "date"
class Date
  def self.new(*args)
    p "new!"
    super *args
  end
  def initialize(*args)
    p "kita!"
  end
  def aaa
    "aaa"
  end
end

p dd = Date.new(2013, 1, 31)
p dd.day
p dd.aaa


$ ruby builtin.rb
"new!"
"kita!"
#<Date: -4712-01-01 ((0j,0s,0n),+0s,2299161j)>
1
"aaa"

今度は来た。
けど、initializeで引数を処理してないのでもう一回。

require "date"
class Date
  def self.new(*args)
    p "new!"
    super *args
  end
  def initialize(*args)
    p "kita!"
    super *args
  end
  def aaa
    "aaa"
  end
end

p dd = Date.new(2013, 1, 31)
p dd.day
p dd.aaa


$ ruby builtin.rb
"new!"
"kita!"
builtin.rb:9:in `initialize': wrong number of arguments(3 for 0) (ArgumentError)

        from builtin.rb:9:in `initialize'
        from builtin.rb:5:in `new'
        from builtin.rb:5:in `new'
        from builtin.rb:16:in `<main>'

まあsuperにぶん投げた所でDateの親の引数とあうかわかんないから当然としても、Dateのinitializeにそのまま投げたいんだけどどうしたらいいかわからない。
そもそも、Dateでもないsuperにぶん投げてるのが間違ってる気がする。
newでインスタンスを作ったときに引数も内部にセットされてるのではないか。
で、initializeはその名の通り初期化するだけと。
newに別名付けて自分自身にぶん投げてみてはどうか。
やってみる。

require "date"
class Date
  alias_method :orgnew, :new
  def self.new(*args)
    p "new!"
    orgnew *args
  end
  def initialize(*args)
    p "kita!"
  end
  def aaa
    "aaa"
  end
end

p dd = Date.new(2013, 1, 31)
p dd.day
p dd.aaa


$ ruby builtin.rb
builtin.rb:3:in `alias_method': undefined method `new' for class `Date' (NameError)
        from builtin.rb:3:in `<class:Date>'
        from builtin.rb:2:in `<main>'

Dateにnewがないって怒られた。
考え方は間違ってない気がしないでもないので「クラスメソッド 別名 ruby」とかでぐぐってみたらいいのがみつかった。

Rubyのクラスメソッドの別名定義(alias) - 山本隆の開発日誌

できるっぽい。
なんか class << self って使ってるけどなんなのかぐぐってみる。

class &lt;&lt; selfを調べてたら特異メソッドとか特異クラスとか出てきた - マグネシウムライト

ざっくりいうと、自分自身に特異クラスを作れるってことらしい。
普段ならselfくっつけて書けばいいじゃんって思ってたはずなので、今回みたいなことがなかったらすごいって思わなかった気がする。
やってみる。

require "date"
class Date
  class << self
    alias_method :orgnew, :new
    def new(*args)
      p "xxx"
      orgnew *args
    end
  end
  def initialize(*args)
    p "kita!"
  end
  def aaa
    "aaa"
  end
end

p dd = Date.new(2013, 1, 31)
p dd.day
p dd.aaa


$ ruby builtin.rb
"xxx"
#<Date: 2013-01-31 ((2456324j,0s,0n),+0s,2299161j)>
31
"aaa"

すごい、きた。
でもinitializeを通ってない。

状況を振り返ると

  • 普通にinitializeをoverrideしても通らなかった
  • superにぶん投げたら通った
  • Dateクラス自身に投げたら通らなくなった
  • 作ったオブジェクトにはinitializeがある(private_methodsで確認)

なぜなのか。
まったくわからない。