日記帳

日記です。

Ruby と遅延初期化

とりあえず遅延初期化が好きです.

遅延初期化ってのはあれです.
普通はコンストラクタでインスタンス変数を初期化して,アクセッサメソッドは初期化済みの値をそのまま返すって書くところを,初期化処理をアクセッサメソッドが最初に呼ばれた場合に実行することで無駄な初期化が起らないようにしようってやつですね.

# 使用前
class NormalHello
	def initialize
		@message = Time::now.strftime("Hello @ %Y-%m-%d")
	end

	attr_reader :message
end
# 使用後
class LaterInitializationHello
	def message
		unless @message then
			@message = Time::now.strftime("Hello @ %Y-%m-%d")
		end
		@message;
	end
end

遅延初期化は以下のような場合に特に有効です.

  • アクセッサメソッドが呼ばれるかもしれないし呼ばれないかもしれない.
  • オブジェクトの初期化処理のコストが大きい.

GUIのダイアログオブジェクトの初期化とかが代表例でしょうか?

でも私の場合初期化処理のコストが大きくない場合にも遅延初期化しまくってしまいます.理由は以下の通り.

でも上のようなコードはアクセサメソッドで毎回条件分岐が起ってします.
分岐は高速化の敵(?)なのでアクセサが頻繁にコールされる場合には効率が悪そうです.

そんなことを考えながら今日書いたのが以下のようなコード.

# 分岐が邪魔なら分岐のないコードに差し替えちゃえばいいじゃない
class LaterInitializationSelfOverrideHello
	def message
		unless @message then
			@message = Time::now.strftime("Hello @ %Y-%m-%d")
			def self.message
				@message
			end
		end
		@message;
	end
end

遅延初期化の初期化処理の後にアクセサメソッド自体をオーバーライドしてしまって分岐のコードが呼ばれないようにするってことですね.これで2回目以降のmessage()呼び出しが分岐一回分高速化する…(はず

動的な言語ってこういうことが簡単にできて素敵ですねぇ…

欠点は,一回以上message()を呼び出した後に他のメソッド等で

@message = nil 

とかされると再初期化してくれないこととかでしょうか.
まぁ条件を変更して @message を再計算させたいような処理の場合は普通に遅延初期化すればいいのですけど…