之前在開發中就發現“dirty”是一種不錯的解決方案:可以用來延緩計算或者避免不必要的計算。后來在想,這應該也算一種設計模式吧,於是搜索“Dirty設計模式”,沒有什么結果,然后換成英文“Dirty design pattern”,搜到了《game programming patterns》這本電子書。書中介紹了Dirty Flag 模式在游戲客戶端的應用場景,如果英文不好,這里也有中文翻譯。本文結合幾個具體的例子,介紹什么是Dirty Flag 模式,並分析該模式的適用場景以及使用注意事項。
什么是Dirty Flag:
簡單來說,就是用一個標志位(flag)來表示一組數據的狀態,這些數據要么是用來計算,或者用來需要同步。在滿足條件的時候設置標志位,然后需要的時候檢查(check)標志位。如果設置了標志位,那么表示這組數據處於dirty狀態,這個時候需要重新計算或者同步。如果flag沒有被設置,那么可以不計算(或者利用緩存的計算結果)。另外,在兩次check之間,即使有多次標志位的設置,也只需要計算一次。
因此,Dirty Flag模式的本質作用在於:延緩計算或數據同步,甚至減少無謂的計算或者同步。計算比較容易理解,對於同步,后面也會給出例子。在后面的描述中,除非特殊說明,計算也包含了同步。
Dirty Flag使用實例:
首先,《game programming pattern》中的例子非常形象生動,圖文並茂,建議直接閱讀原文,本文不再復述。接下來介紹幾個其他的例子。
First
1 def set_need_tick(self, is_need): 2 self.need_tick = is_need 3 4 def tick(self): 5 if self.need_tick: 6 self.do_tick() # do_tick 需要做大量的檢查,較為耗時
1 def dummy_tick(self): 2 pass 3 def set_need_tick(self, is_need): 4 if is_need: 5 self.tick = self.do_tick 6 else: 7 self.tick = self.dummy_tick
Second
1 class cached_property(object): 2 """ A property that is only computed once per instance and then replaces 3 itself with an ordinary attribute. Deleting the attribute resets the 4 property. """ 5 6 def __init__(self, func): 7 update_wrapper(self, func) 8 self.func = func 9 10 def __get__(self, obj, cls): 11 if obj is None: return self 12 value = obj.__dict__[self.func.__name__] = self.func(obj) 13 return value
1 def set_property_dirty(self, property_name): 2 self.__dict__.pop(property_name, None)
在需要的時候調用這個設置函數就行了,在這個例子中,並沒有對某個屬性的設置和檢查,但配合之前的cached_property,作用是很明顯的:緩存計算結果,需要的時候重新計算。下面是完整測試代碼

1 import functools, time 2 class cached_property(object): 3 """ A property that is only computed once per instance and then replaces 4 itself with an ordinary attribute. Deleting the attribute resets the 5 property. """ 6 7 def __init__(self, func): 8 functools.update_wrapper(self, func) 9 self.func = func 10 11 def __get__(self, obj, cls): 12 if obj is None: return self 13 value = obj.__dict__[self.func.__name__] = self.func(obj) 14 return value 15 16 class TestClz(object): 17 @cached_property 18 def complex_calc(self): 19 print 'very complex_calc' 20 return sum(range(100)) 21 22 def __set_property_dirty(self, property_name = 'complex_calc'): 23 self.__dict__.pop(property_name, None) 24 25 def some_action_effect_property(self): 26 self.__set_property_dirty() 27 28 29 30 if __name__=='__main__': 31 t = TestClz() 32 print '>>> first call' 33 print t.complex_calc 34 print '>>> second call' 35 print t.complex_calc 36 print '>>>third call' 37 t.some_action_effect_property() 38 print t.complex_calc