在一個應用軟件的成型過程中,一些意想不到的商業邏輯到處出現。比如,基於價格的考慮,這個任 務必須減少項目;而那個任務也因為銷售稅而必須選擇合適的比率;而其它的任務也必須因為其他的特 別條件而終止。一些商業規則是簡單的,只需要不到一兩個布爾比較關系就夠了,然而它的規則可能需 要費時的估計,需要查詢數據庫或者用戶輸入數據來引導。
通過書寫代碼可以把抽象(比如一條 商業規則)轉化為具體可見的東西。但是抽象物(比如購物方式,稅率,或者計算海運費等等)都有其 進化的方式,而且這些改變很容易難倒一個不幸運的開發人員。為了保證安全可靠——到目 前為止你在這本書所看到的——盡可能的分離封裝那些容易改變的模塊是個很完美的想法。 而且,這的確也一個明智的應對商業規則的策略。
問題描述
有沒有明確的方式來封裝商 業邏輯呢?有沒有一個容易改寫和重用的技術呢?
解決方案
規范模式是為驗證和選擇而 開發的:
確認一個特殊的對象是否滿足一定的標准
從集合中選擇出滿足給定標准的元素 。
規范模式能讓你有效的組織這些標准,並在你的應用程序中靈活的使用他們。
代碼重 構技術已經激發你的興趣,你決定使用它來提升代碼的清晰度和重用性。規范模式通過系統化進一步的 深化了這一步,它系統把這個結構分解成一個個單獨的對象,這些對象能夠很方便的插入到你的應用程 序的合適地方。很多情況下,在你的應用程序裡,規范對象是參數化的,而且經常被組合在一起來構建 復雜的合乎邏輯的表達式。
相關知識
Eric Evans 和 Martin Fowler 發表過一篇關於規 范模型的文章,地址是:http://www.martinfowler.com/apsupp/spec.pdf
這個模式在Eric Evans的書本《動態驅動設計》(“Domain Driven Design”)的第224到273頁有詳細的介紹。
為了合理的全面覆蓋這個模式,這章被組織成合乎邏輯的三部分。第一部分通過一個純粹的實例 來說明基本的模式概念。(Evans 和 Fowler 把這個稱為為“硬編碼規范Hard Coded Specification”)。接下來的部分演示了如何構建一個參數化規范模型,它提供了一個更加動態 和靈活的框架來實現規范模式(或者因此而稱為“參數化規范”)的重用。最後一部分,我 們開發了一個“方案工廠”(Policy Factory),它把許多規范對象集中成一個易於使用的包 (package)。
Traveling to Warm Destinations(到溫暖的目的地去旅行)
最近,我和我 的家人計劃去度一個假期,我的妻子想去一個“溫暖的地方”。雖然有無數旅行相關的站點 ,但是在我們訪問過的站點中沒有一個站點能夠為每一個目的地提供詳細的天氣信息。沒辦法,我們不 得不轉到weather.com然後開始搜索,這是十分的不方便的。現在讓我們來改變這種情況,為一個假定的 旅行站點增加一個天氣搜索功能。在這裡我們是用規范模式這個指南來引導你編碼,從而比較旅行者期 望的最低溫度和許多目的地的平均溫度
首先,我們創建一些非常簡單的對象。第一個是旅行者( a Traveler),它存儲了首選的最低溫度。
// PHP5
class Traveler {
public $min_temp;
}
接下來我們創建一個對象來表示目的地(Destination)。 由於平均溫度是一個關鍵的標准,目的地的構建函數(__constructor)應該得到一個十二維的數組,該 數組的每一個值對應一年裡面每個月的平均溫度。
class Destination {
protected $avg_temps;
public function __construct($avg_temps) {
$this->avg_temps = $avg_temps;
}
}
目的地(Destination)同樣也還要一個方法,通過調用這 個方法能夠得到這個目的地在指定月份的平均溫度。
class Destination {
//...
public function getAvgTemPByMonth($month) {
$key = (int)$month - 1;
if (array_key_exists($key, $this->avg_temps)) {
return $this->avg_temps [$key];
}
}
}
最後,一次旅行(類Trip)就由一個旅行者(類 Traveler),一個目的地(類Destination)和一個日期(a Date)聯合組成。
class Trip {
public $date;
public $traveler;
public $destination;
}
給出上面這些對象,你就可以通過Trip::date得到旅行的月份,並且你能夠比較目的 地的月平均溫度和旅行者期望的最低溫度。(這個比較可能不是特別的復雜,但是你還是需要你自己親 自去實現)
讓我們看看如何用規范模式實現“溫暖目的地”的商業邏輯,並且看看如 何應用這個模式來驗證每一個目的地並選擇出所有合適的目的地。
樣本代碼
規范模式的 核心是一個帶有IsSatisfiedBy()方法的對象,IsSatisfiedBy()方法接收一個變量來評估並且返回一個 基於規范標准的布爾值。
“目的地是足夠溫暖的”的標准可能就是:
class TripRequiredTemperatureSpecification {
public function isSatisfiedBy($trip) {
$trip_temp = $trip->destination->getAvgTemPByMonth(
date(‘m’, $trip->date));
return ($trip_temp >= $trip->traveler ->min_temp);
}
}