MobX詳解(一):裝飾者模式

九死蠶傳人bo發表於2018-01-30

當我們想要擴充套件一個物件的能力時,通常可以通過新增原型方法,修改建構函式,繼承等方式。除此之外,我們還可以通過妝飾者模式來達到目的。

例如一個遊戲角色,我們在不改變這個角色物件的條件下,給角色穿一件裝備(武器),那麼角色的屬性(攻擊力)就會增加。這個過程,就可以由妝飾者模式來完成。

我們通過一個例子來演示。

首先我們有幾件裝備,他們的資訊儲存在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);  // 檢視變化

從下圖我們可以看到具體的變化,說明裝飾成功了。

clipboard.png

除此之外,我們還需要建立武器裝飾類與鞋子裝飾類,武器與鞋子的穿戴會改變角色的攻擊動作與奔跑動作,因此需要多行為進行更改,如下:

// 武器裝飾類
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);

clipboard.png

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的實現。歡迎大家留言提供思路。

clipboard.png

相關文章