當我們想要擴充套件一個物件的能力時,通常可以通過新增原型方法,修改建構函式,繼承等方式。除此之外,我們還可以通過妝飾者模式來達到目的。
例如一個遊戲角色,我們在不改變這個角色物件的條件下,給角色穿一件裝備(武器),那麼角色的屬性(攻擊力)就會增加。這個過程,就可以由妝飾者模式來完成。
我們通過一個例子來演示。
首先我們有幾件裝備,他們的資訊儲存在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的實現。歡迎大家留言提供思路。