在編寫面向對象的代碼的時,有些時候你需要一個能夠自己根據不同的條件來引入不同的操作對象實 例。例如,一個菜單功能能夠根據用戶的“皮膚”首選項來決定是否采用水平的還是垂直的 排列形式,或者一個計費系統可以自行根據用戶的收貨地址來決定稅率。
一般來講,一個控制菜 單的對象實例包括了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 {
var $_name;
function VarCache($name) {
$this->_name = ‘cache/’.$name;
}
function isValid() {
return file_exists ($this->_name.’.php’);
}
function set() {
$file_handle = fopen($this->_name.’.php’, ‘w’);
fclose($file_handle);
}
}