在編寫面向對象的代碼的時,有些時候你需要一個能夠自己根據不同的條件來引入不同的操作對象實例。例如,一個菜單功能能夠根據用戶的“皮膚”首選項來決定是否采用水平的還是垂直的排列形式,或者一個計費系統可以自行根據用戶的收貨地址來決定稅率。
一般來講,一個控制菜單的對象實例包括了add(), delete(), 和 replace()等菜單元素;并通過set()進行配置,用render()來管理顯示模式。無論你想生成什么樣子的菜單,你都可以用同一個對象類來處理。不同菜單的對象實例只是一些方式函數的運算規則不同罷了,至少在剛才的例子里面render()函數是不同的。
但是如果你需要增加菜單的顯示模式種類,或者你需要根據用戶的國家、省份等信息來判斷菜單排列的順序的時候,該怎么做呢?而且如果有許多的方式函數都是經常變化的,那么簡單的類封裝將變得復雜、難易理解和升級的。
問題
怎么輕松地改變對象實例的執行過程,因而在代碼執行的時候動態地改變執行過程?一旦實現了這個功能,如果去編寫這樣的類定義從而讓維護和升級變得非常簡單呢?
解決辦法
當一個類封裝了多個操作的時候,對象實例可以動態地選擇這些操作來進行,可以用策略模式來把對象本身和運算規則區分開來。或者,更簡單的處理是類里面定義的方式函數用case語句來進行控制。當然更簡單的方法是使用策略模式。
策略模式功能非常強大,因為這個設計模式本身的核心思想就是面向對象編程的多形性的思想。
就在編程領域之外,有許多例子是關于策略模式的。如果我需要在清晨從家里去上班,我可以有幾個策略可以考慮:我可以開車,乘坐公交車,走路,汽車或者甚至是搭乘直升飛機。每個策略都可以得到相同的結果,但是它們使用了不同的資源。選擇策略的依據是費用,時間,使用工具還有每種方式的方便程度 。一個很好的策略也許在第二天就不能再被使用的,所以策略的選擇是相對的。
你已經在前面的工廠模式章節看到了和策略模式相似的例子:因為不同特性的費用計算方式不同,所以Monopoly游戲的框架使用了許多相似的特性類,但是因為費用的計算不是從類本身獲得,所以這個費用計算相對來說是一個TemplateMethod 設計模式。
例子
舉例子說明,讓我們做一個存儲PHP參數的cache。這個cahce類需要把變量以PHP識別的方式寫入到一個文件當中,所以你可以在以后加載該文件并使用它。這個類還應該可以讓你為每個數據加個標識符和存儲的方式。
數據緩存
注:緩存是為了在接下來的操作中繼續使用而對資源進行緩存。你可以通過建立和使用緩存來節省直接從原數據庫獲取數據的時間。這方面的例子最常見的就是訪問數據庫或者解析大的XML文檔,或者大的配置文件。
緩存也會出現一個問題:你的緩存可能會失去與原數據的同步。或者緩存需要使用太多內存。
最開始,我們開發一個緩存操作,并不使用策略模式。
因為你可能需要緩存的不止一個值,所以你需要使用標識符來標識出你需要指定的元素。在這個例子中,標識符就是’application_config’。下面試一個如果使用cache的例子。
| // PHP4 $config_cache =& new VarCache(‘application_config’); if ($config_cache->isValid()) { $config = $config_cache->get(); } else { $config = slow_expensive_function_to_get_config(); $config_cache->set($config); } |
這個代碼生成了一個新的VarCache對象存放在$config_cache變量里面。這個數據在緩存中的標識符是 ‘application_config’。如果在緩存里面有這個數據, isValid() 將返回真( true )并且獲取緩存中的數據。反之,值被重新獲取并寫入緩存當中,以便下次使用。
按照一般的需求,讓我們開始編寫這段代碼來進行測試。首先,如果緩存中沒有該數據, isValid() 方式函數應該返回非值(false)。
| class VarCacheTestCase extends UnitTestCase { function TestUnsetValueIsInvalid() { $cache =& new VarCache(‘foo’); $this->assertFalse($cache->isValid()); } |
因為VarCache現在沒有代碼,所以最簡單的方式就是先構造一個方式函數。
| class VarCache { function isValid() {} } |
這樣,我們就可以繼續了。
| class VarCacheTestCase extends UnitTestCase { function TestUnsetValueIsInvalid() { /* ... */ } function TestIsValidTrueAfterSet() { $cache =& new VarCache(‘foo’); $cache->set(‘bar’); $this->assertTrue($cache->isValid()); } |
上面的測試校驗了緩存的數據是否是可用的。
開始編寫cache類的主要部分。VarCache 引入一個標識符, 所以constructor了一個應該記錄它的對象實例。這里面還有一個set的方式函數,用來把數據存入緩存,或者當數據存在時,修改緩存當中的數據。
| class VarCache { |
運行這個測試來產生一個我們預期的結果;但是實際情況是報錯!為什么呢?第一次運新的時候沒有生成文件,所以第二次運行的時候找不到文件,顯然我們不希望這種情況出現。我們期望的是每一次運行代碼都是互不影響的。
幸運的是,把總體測試框架和特定功能的簡單測試結合起來,我們就可以得到靈活的測試環境,并且在以后的測試中方便地使用。UnitTestCase::setUp()實現框架的初始化,而UnitTestCase::tearDown()實現具體的測試過程。
新聞熱點
疑難解答