当我们想要扩展一个对象的能力时,通常可以通过添加原型方法,修改构造函数,继承等方式。除此之外,我们还可以通过妆饰者模式来达到目的。
例如一个游戏角色,我们在不改变这个角色对象的条件下,给角色穿一件装备(武器),那么角色的属性(攻击力)就会增加。这个过程,就可以由妆饰者模式来完成。
我们通过一个例子来演示。
首先我们有几件装备,他们的信息保存在config.js
中,如下:
// config.js
export const cloth = {
name: '七彩炫光衣',
hp: 1000
}
export const weapon = {
name: '青龙偃月刀',
attack: 2000
}
export const shoes = {
name: '神行疾步靴',
speed: 300
}
export const defaultRole = {
hp: 100,
atk: 50,
speed: 125,
cloth: null,
weapon: null,
shoes: null,
career: null,
gender: null
}
然后创建一个基础的角色对象。
// 基础角色
// 有血条,攻击力,速度三个基础属性
// 以及衣服,武器,鞋子三个装备插槽
var Role = function(role) {
this.hp = role.hp;
this.atk = role.atk;
this.speed = role.speed;
this.cloth = role.cloth;
this.weapon = role.weapon;
this.shoes = role.shoes;
}
在原型上添加奔跑和攻击两个共有方法。
Role.prototype = {
constructor: Role,
run: function() {},
attack: function() {}
// ...
}
引入配置文件中的准备与默认角色
import { cloth, weapon, shoes, defaultRole } from './config';
创建职业为战士的角色对象。
var Soldier = function(role) {
var o = Object.assign({}, defaultRole, role);
Role.call(this, o); // 构造函数继承
this.nickname = o.nickname;
this.gender = o.gender;
this.career = '战士';
if (role.hp == defaultRole.hp) {
this.hp = defaultRole.hp + 20;
} // 战士的移动血条 + 20
if (role.speed == defaultRole.speed) {
this.speed = defaultRole.speed + 5;
} // 战士的移动速度 + 5
}
// 原型的继承
Soldier.prototype = Object.create(Role.prototype, {
constructor: {
value: Soldier,
},
run: {
value: function() {
console.log('战士的奔跑动作');
},
},
attack: {
value: function() {
console.log('战士的基础攻击');
}
}
// ...
})
接下来我们要创建装饰类。
因为装饰类可能会有很多,衣服鞋子武器都肯定各有一个装饰类来分别负责不同的行为与变化,所以我们需要几个基础装饰类。通常情况下,装饰类与被装饰的类有一些相似的地方,大家可以自行体会其中的差异,如下:
// 基础装饰类
var Decorator = function(role) {
this.role = role;
this.hp = role.hp;
this.atk = role.atk;
this.speed = role.speed;
this.cloth = role.cloth;
this.weapon = role.weapon;
this.shoes = role.shoes;
this.career = role.career;
this.gender = role.gender;
this.nickname = role.nickname;
}
Decorator.prototype = {
constructor: Decorator,
run: function() {
this.role.run();
},
attack: function() {
this.role.attack();
}
// ...
}
我们可以看到,基础装饰类以一个角色对象作为构建基础,并没有对角色对象做进一步改变。因此,具体的改变肯定是在具体的装饰类中进行的。
接来下创建一个衣服的装饰类,ClothDectorator
,我们的例子中,装备一件衣服并不会修改其行为,只是增加属性,代码如下:
var ClothDecorator = function(role, cloth) {
Decorator.call(this, role);
this.cloth = cloth.name;
this.hp += cloth.hp;
}
衣服装饰类继承基础装饰类,并增加一个装备对象作为构建基础,在构造函数内部,新增了衣服插槽this.cloth
与增加了血条。
我们在具体使用中感受一下具体变化:
var base = {
...defaultRole,
nickname: 'alex',
gender: 'man'
}
var alex = new Soldier(base); // 新建一个战士角色
alex.run(); // 跑一跑
alex.attack(); // 攻击一下
console.log(alex); // 查看alex对象
alex = new ClothDecorator(alex, cloth); // 装备衣服
console.log(alex); // 查看变化
从下图我们可以看到具体的变化,说明装饰成功了。
除此之外,我们还需要创建武器装饰类与鞋子装饰类,武器与鞋子的穿戴会改变角色的攻击动作与奔跑动作,因此需要多行为进行更改,如下:
// 武器装饰类
var WeaponDecorator = function(role, weapon) {
Decorator.call(this, role);
this.weapon = weapon.name;
this.atk += weapon.attack;
}
WeaponDecorator.prototype = Object.create(Decorator.prototype, {
constructor: {
value: WeaponDecorator
},
attack: { // 修改攻击方法
value: function() {
console.log('装备了武器,攻击变得更强了');
}
}
})
// 鞋子装饰类
var ShoesDecorator = function(role, shoes) {
Decorator.call(this, role);
this.shoes = shoes.name;
this.speed += shoes.speed;
}
ShoesDecorator.prototype = Object.create(Decorator.prototype, {
constructor: {
value: ShoesDecorator
},
run: { // 修改奔跑方法
value: function() {
console.log('穿上了鞋子,奔跑速度更快了');
}
}
})
角色alex穿了衣服之后,我们还可以继续为他穿上鞋子与武器。代码如下:
console.log(' ');
console.log('------装备武器-----');
alex = new WeaponDecorator(alex, weapon); // 装备武器
alex.attack();
console.log(alex);
console.log(' ');
console.log('------装备鞋子-----');
alex = new ShoesDecorator(alex, shoes); // 装备鞋子
alex.run();
console.log(alex);
OK,这就是整个装饰者模式的思路与具体实现,
用ES6的class实现,源代码如下:
import { cloth, weapon, shoes, defaultRole } from './config';
// 基础角色
class Role {
constructor(role) {
this.hp = role.hp;
this.atk = role.atk;
this.speed = role.speed;
this.cloth = role.cloth;
this.weapon = role.weapon;
this.shoes = role.shoes;
}
run() {}
attack() {}
}
class Soldier extends Role {
constructor(roleInfo) {
const o = Object.assign({}, defaultRole, roleInfo);
super(o);
this.nickname = o.nickname;
this.gender = o.gender;
this.career = '战士';
if (roleInfo.hp == defaultRole.hp) {
this.hp = defaultRole.hp + 20;
}
if (roleInfo.speed == defaultRole.speed) {
this.speed = defaultRole.speed + 5;
}
}
run() {
console.log('战士的奔跑动作');
}
attack() {
console.log('战士的基础攻击');
}
}
// class Mage extends Role {}
class Decorator {
constructor(role) {
this.role = role;
this.hp = role.hp;
this.atk = role.atk;
this.speed = role.speed;
this.cloth = role.cloth;
this.weapon = role.weapon;
this.shoes = role.shoes;
this.career = role.career;
this.gender = role.gender;
this.nickname = role.nickname;
}
run() { this.role.run(); }
attack() { this.role.attack() }
}
class ClothDecorator extends Decorator {
constructor(role, cloth) {
super(role);
this.cloth = cloth.name;
this.hp += cloth.hp;
}
}
class WeaponDecorator extends Decorator {
constructor(role, weapon) {
super(role);
this.weapon = weapon.name;
this.atk += weapon.attack;
}
attack() {
console.log('装备了武器,攻击变得更强了');
}
}
class ShoesDecorator extends Decorator {
constructor(role, shoes) {
super(role);
this.shoes = shoes.name;
this.speed += shoes.speed;
}
run() {
console.log('穿上了鞋子,奔跑速度更快了');
}
}
const baseInfo = {
...defaultRole,
nickname: 'alex',
gender: 'man'
}
let alex = new Soldier(baseInfo);
alex.run();
alex.attack();
console.log(alex);
console.log(' ');
console.log('------装备衣服-----');
alex = new ClothDecorator(alex, cloth);
console.log(alex);
console.log(' ');
console.log('------装备武器-----');
alex = new WeaponDecorator(alex, weapon);
alex.attack();
console.log(alex);
console.log(' ');
console.log('------装备鞋子-----');
alex = new ShoesDecorator(alex, shoes);
alex.run();
console.log(alex);
除了角色与装备之间的关系可以用装饰者模式来搞定之外,我们在玩游戏的时候,还知道每个角色都会在某些情况下获得不同的buff,例如大龙buf,小龙buf,红buff,蓝buff等,这些buff有的会更改角色属性,例如cd更短,攻击更高,有的会更改攻击特性,例如红buff会持续掉血,减速等,这些buff还有持续时间,大家可以思考一下,如何使用装饰者模式来完成这些buff的实现。欢迎大家留言提供思路。