13

当我们想要扩展一个对象的能力时,通常可以通过添加原型方法,修改构造函数,继承等方式。除此之外,我们还可以通过妆饰者模式来达到目的。

例如一个游戏角色,我们在不改变这个角色对象的条件下,给角色穿一件装备(武器),那么角色的属性(攻击力)就会增加。这个过程,就可以由妆饰者模式来完成。

我们通过一个例子来演示。

首先我们有几件装备,他们的信息保存在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


这波能反杀
12.6k 声望2.7k 粉丝