在角色扮演类游戏,常常涉及到某个英雄装配各种装备,例如大掌门,我叫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']) {
}
}
这样做的好处是,到时候如果需要修改装备时,我们只需要修改装备类中的方法。