Why's Lucky Stiff 上看來的。俺只是搬運工。能讀原文的老大們不用往下看了。
嗯,假如一個類裡有個執行個體方法。我們希望這個方法只運行一次。”切,我還以為是搶雞蛋呢“,熟讀鐵撬書的老大們開始嗤之以鼻,”不就是第391頁裡Tadayoshi Funaba的once嗎?就連上一篇《吃了大力丸的Ruby》也有類似的實現”:
01: def once(*ids)
02: for id in ids
03: module_eval <<- "end;"
04: alias_method :__#{id.to_i}__, :#{id.to_s}
05: private :__#{id.to_i}__
06: def #{id.to_s}(*args, &block)
07: (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
08: end
09: end;
10: end
11: end
那有沒有其它的方法,繞開這種meta-programming的常見技巧呢?於是__Why老大提出了下面這個方法:
01: class Trial
02: def run_me
03: def self.run_me; raise Exception, "NO MORE." end
04: puts "Your trial period has ended."
05: end
06: end
07:
08: t = Trial.new
09: t.run_me
10: #=> Your trial period has ended.
11: t.run_me
12: #=> (trial):3:in `run_me': NO MORE. (Exception)
注意03行。在方法run_me()裡,我們重新定義了run_me()。因為定義函數也是動態執行,當run_me()第一次運行時,03行被執行,導致新的run_me()被定義。接著04行被執行,列印出"Your trial period has ended"。函數run_me()執行完畢後,新定義取代老定義,再執行就得到第12行的結果了。注意03行用了self.run_me。這樣run_me()在instance的範疇內被改寫,所以我們可以不斷建立新的Trail執行個體,裡面的run_me()可以運行至少一次。如果去掉self,run_me()會在class層級被重寫。導致run_me()只能被第一個Trial的執行個體執行一次。
從上面我們還可以印證一個細節:Ruby裡的def..定義的方法的scope和執行這個定義的方法無關。執行定義的方法和被定義的方法的scope只取決於它們的context. 這點和JavaScript或Python都不一樣。下面一段Ruby代碼可為例證:
01: def foo
02: def bar
03: puts "bar"
04: end
05: puts "foo"
06: end
07:
08: bar
09: #=>NameError: undefined local variable or method `bar'
10: foo
11: #=>foo
12: bar
13: #=>bar
這個技術自然可以應用到記憶最佳化上:
01: class Hit
02: def initialize(ip)
03: @ip = ip
04: end
05: def country
06: def self.country; @country end
07: @country = `geoiplookup #{@ip}`.chomp.gsub(/^GeoIP Country Edition: /,"")
08: end
09: end
後來有人評論道,把被記憶的資料轉換成accessor速度更快。我一般對這種語言層級的最佳化不感冒。沒有測量的情況下,再怎麼也輪不到這種局部微調啊。不過考慮到Ruby現在的速度真的有點慢(那個soap4r簡直要把人氣死),還是值得提一下:
1:def country
2: class << self ; attr_reader:countryend
3: @country = `geoiplookup #{@ip}`.
4: chomp.gsub(/^GeoIP Country Edition: /,"")
5: end
為什麼用attr_reader就快呢?因為產生的attr_reader被放在了包含它的類的解析樹之外。用它時省去了動態尋找的時間:
01: class X
02: attr_reader :x
03: def y; @y; end
04: end
05: [[:class,
06: :X,
07: :Object,
08: [:defn, :x, [:ivar, :@x]],
09: [:defn, :y, [:scope, [:block, [:args], [:ivar, :@y]]]]]]
運用自我更新的方法,還可以實現無需對象的諸如有限自動機或狀態模式。是的,我沒有胡言亂語。不用對象,一樣可以實現狀態模式。具體的實現和背後的理論,就留待下一片大力丸了。性急的老大們可以到這裡看例子。