日記帳

日記です。

Ruby で遅延初期化処理付き getter を一発定義(2)

http://d.hatena.ne.jp/sa-y/20051217 のコメントを参照.

なかださん最高♪ 私のへちょいプログラムとは比べものにならないですねぇ…
きっと世界中の遅延初期化マニアが抱き付いてキスしてきますよ?

インデントが崩れてるのとインスタンス変数が初期化済みの場合にnilを返してしまうのだけ修正したものを以下に置きます.

# なかださん作の遅延初期化処理付き Module#attr_reader()
class Module
	alias original_attr_reader attr_reader

	def attr_reader(*names)
		unless block_given?
			return original_attr_reader(*names)
		end

		names.each do |name|
			ivar = "@#{name}"
			define_method(name) do
				value = instance_variable_get(ivar)
				unless value
					value = yield self
					instance_variable_set(ivar, value)
				end
				class << self; self; end.class_eval do
					original_attr_reader name
				end
				value
			end
		end
	end
end

if $0 == __FILE__
	class Hello
		attr_reader(:message) {
			Time::now.strftime("Hello @ %Y/%m/%d")
		}
	end

	class Hello2
		def create_message
			Time::now.strftime("Hello @ %Y/%m/%d")
		end

		attr_reader(:message) {|obj| obj.create_message}
	end

	p h = Hello.new, h2 = Hello2.new
	p h.message, h2.message
	p h.message, h2.message
	p h, h2
end

私のに比べた改善点は3つくらい.

  1. メソッド定義しておいて alias で差し替える処理から attr_reader で再定義するようになって無駄にメソッド名を浪費しないように変更
  2. いかにも無駄っぽかったinstance_eval()が特異クラスに対するclass_eval()に変更
  3. yield に self を渡すことでブロック内でインスタンスメソッドが呼べるように変更

2.は

class << self; self; end

で特異クラスのオブジェクトが取り出せるってのはどこかで読んで知っていたんだけど,これに class_eval() を組み合せるのは思い付かなかった.

3.はインスタンスメソッド呼べないと役に立たないと思いっていたのに思い付かなかった.Ruby的にはよく見かけるイディオムなのになぁ…

うーん,勉強になるなぁ.