在角色扮演類遊戲,常常涉及到某個英雄裝配各種裝備,例如大掌門,我叫MT這樣的遊戲中,角色都可以裝配一些裝備,如,武器,護甲等。
當角色裝配各種裝備後,會使角色的某些屬性增加,例如,金蛇郎君裝備金蛇劍後,他的攻擊力可以增加500,再給他搞個軟蝟甲穿上,他的防禦力直接增加400。如果機緣巧合,他學會如來神掌,那他的攻擊力直接飆升到1000.當角色裝配某種“道具”後,他的屬性都會相應的發生改變。
在遊戲中,一般情況下,角色可以隨意搭配裝備。還是那金蛇郎君舉例,給他 裝配了 金蛇劍, 軟蝟甲,如來神掌,他在數據庫存儲的記錄可能類似於這樣:
name: 金蛇郎君
weapon_id: 003 (對應金蛇劍)
armor_id: 004 (對應軟蝟甲)
kongfu_id: 005 (對應如來神掌)
我們計算金蛇郎君的整體實力的時候 代碼可能是這樣:
function getUserPower($user)
{
// 配置 武器
if ($user['weapon_id']) {
if ($weapon = $this->_user->weapon->getOneWeapon($user['captain_id'])) {
// 金蛇郎君的戰鬥力增加
$user['attack'] += $captain['attack'];
// 金蛇郎君的防禦力增加
$user['defend'] += $weapon['defend'];
} else {
// 不存在則強行卸載
$this->unloadItem($user, 'weapon');
unset($weapon['weapon']);
}
}
// 配置 護甲
if ($user['armor_id']) {
if ($armor = $this->_user->armor->getOneArmor($user['armor_id'])) {
// 金蛇郎君的戰鬥力增加
$user['attack'] += $captain['attack'];
// 金蛇郎君的防禦力增加
$user['defend'] += $weapon['defend'];
} else {
// 不存在則強行卸載
$this->unloadItem($user, 'armor_id');
unset($user['armor_id']);
}
}
樣的代碼,似乎可以完美的搞定這個需求,但是,某一天,坑爹的產品再次放出話來:夏雪宜裝配金蛇劍後,金蛇劍不僅可以“增加”夏雪宜的攻擊力,還能“增加”他的防禦力,除此之外,裝備對角色屬性影響的加成算法發生了變化。
遇到這樣的產品,我們仰天長嘆之後,還得充滿辛酸的修改角色類中getUserPower()方法。改就改吧,這又有什麼關係呢,問題就在這裏:由於金蛇劍這個裝備發生了變化,導致我不得不去改角色類裏面的方法!!如果哪一天,我增加了一個裝備類型,或者某個裝備的加成方式發生了變動,那我的角色類中的getUserPower()函數要改的面目全非。
這樣,角色模塊和裝備模塊耦合太高,裝備的改動易導致角色模塊出現錯誤的風險增加。有沒有一種好的設計模式能解決這個問題,當裝備發生變動的時候,我只需要改裝備類裏面的方法,而儘量少改動角色類裏面的getUserPower()方法。
這個時候,我們可以使用裝飾模式 。裝飾模式動態的把責任附加到對象上,若要擴展功能,裝飾者比繼承提供更有彈性的替代方案。在裝飾對象的時候,裝飾模式要求被裝飾者和裝飾者需要繼承同一個超類。
這是裝飾者模式的類圖:
這裏,我們需要把焦點定格在裝飾這個詞彙上,所謂裝飾,就是那一樣東西就裝飾另一樣東西,在不改變另一樣東西的前提下,修改它的某些特徵,或者給他添加的新得職能。我們這裏,就可以參考以上的類圖形式,使用 金蛇劍類,軟蝟甲類,武功類分別裝飾金蛇郎君。
類關係圖如下:
我們就是用裝備類來裝飾角色類,此遊戲的裝飾模式其實和定義的裝飾模式有點區別,裝飾模式要求被裝飾者和裝飾者是繼承同一個類,而我們的裝備類與角色類並不是繼承同一個父類。類圖如下:
每個裝備類,都必須實現一個equip(Model_User $user)方法,這個方法的參數是一個角色對象:
/**
* 裝備-武器模型
*
* $Id: weapon.php 1953 2013-04-10 06:33:38Z jiangjian $
*/
class Model_Item_Weapon extends Model_Item_Abstract
{
/**
* 裝配到角色上
*
* @param Model_User $user
* @return void
*/
public function equip(Model_User $user)
{
// 裝備屬性賦值
$user['weapon'] = $this;
// 增加 攻擊力
$user['offense'] += $this->_prop['offense'];
// 增加 射速
$user['fire_rate'] += $this->_prop['fire_rate'];
// 增加 命中率
$user['hit_odds'] += $this->_prop['hit_odds'] ;
}
}
在角色類中,我們需要一個方法:
class Model_User
{
/**
* 裝飾、配置我的角色(即計算裝備屬性增益)
*
* @param Model_User $user
* @param array $userInfo 我的配置
* @return void
*/
private function _decorateUser(Model_User $user, &$userInfo)
{
// 配置 武器
if ($userInfo['weapon_id']) {
if ($weapon = $this->_user->Weapon->getOneWeapon($userInfo['weapon_id'])) {
// 實現對角色的裝飾
$weapon->equipUser($user);
} else {
// 不存在則強行卸載
$this->unloadItem($userInfo, 'weapon');
unset($userInfo['weapon_id']);
}
}
// 配置 護甲
if ($userInfo['armor_id']) {
}
// 配置 功夫
if ($userInfo['kongfu_id']) {
}
}
這樣做的好處是,到時候如果需要修改裝備時,我們只需要修改裝備類中的方法。