JS設計模式之工廠模式


一般RPG游戲里, 一開始我們都要創建角色,選擇職業,戰士、法師還是弓箭手

var Character = function () {
    },
    Warrior = klass(Character),
    Mage = klass(Character),
    Archer = klass(Character),

    Player = function () {
    };
    
Character.prototype.level = function () {
};
Character.prototype.gather = function () {
};
Character.prototype.fight = function () {
};

Player.prototype.play = function (role) {
    var character;
    switch (role) {
        case "戰士":
            character = new Warrior();
            break;
        case "法師":
            character = new Mage();
            break;
        case "弓箭手":
            character = new Archer();
            break;
        default:
            character = new Warrior();
    }
    character.level();
    character.gather();
    character.fight(); }; 
var player = new Player(); player.play(
"法師");

Klass相當於extend的實現

var klass = (function () {
    var F = function () {
    };

    return function (Parent) {
        var Child;

        Child = function () {
            Child.superproto.constructor.apply(this, arguments);
        };

        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.prototype.constructor = Child;
        Child.superproto = Parent.prototype;

        return Child;
    };
})();

這樣寫的問題在於,如果我們想要再加入一個新職業,比如盜賊,我們不得不找到Player,打開它的代碼,給switch增加一個case,另外,如果還有PlayerVIP1,PlayerVIP2,PlayerVIP3...都在自己內部創建角色,那每一處都需要修改,也就是要修改客戶端的代碼,這聽上去就讓人覺得不太好,更好的辦法是把創建角色的工作交給一個簡單工廠來做。

這里值得一提的是,在教科書中顯然會定義一個CharacterFactory類,但在JS里呢,是定義一個對象好還是一個構造函數好呢,我有點小糾結,感覺沒有什么不同,書上說有時CharacterFactory類里直接定義靜態方法,那么可能更接近對象一些,當然構造函數也是對象,給構造函數一個屬性方法的話看起來是一樣的。

簡單工廠

var CharacterFactory = {
    createCharacter: function (role) {
        var character;
        switch (role) {
            case "戰士":
                character = new Warrior();
                break;
            case "法師":
                character = new Mage();
                break;
            case "弓箭手":
                character = new Archer();
                break;
            default:
                character = new Warrior();
        }
        return character;
    }
};

Player.prototype.play = function (role) {
    var character = CharacterFactory.createCharacter(role);
    character.level();
    character.gather();
    character.fight();
};

現在只需要修改工廠對象就可以了,客戶端代碼保持不變Character.createCharacter(),也不是嚴格的不變,因為”戰士“或”盜賊“的判斷還是放在客戶端,但是新增盜賊職業不還是要修改switch加一個case嗎!這也違背了開閉原則。這時工廠方法(Factory Method)就登場了。

工廠方法

先定義一個工廠接口,這個接口定義了一個工廠方法來創建某一類型的產品,然后有任意數量的具體工廠來實現這個接口,在各自的工廠方法里創建那個類型產品的具體實例(TODO:這個優點我還沒有太理解)。

var WarriorFactory = function() {},
    MageFactory = function() {},
    ArcherFactory = function() {};

WarriorFactory.prototype.createCharacter = function() {
    return new Warrior();
};
MageFactory.prototype.createCharacter = function() {
    return new Mage();
};
ArcherFactory.prototype.createCharacter = function() {
    return new Archer();
};

Player.prototype.play = function(role) {
    var factory, character;
    switch (role) {
        case "戰士":
            factory = new WarriorFactory();
            break;
        case "法師":
            factory = new MageFactory();
            break;
        case "弓箭手":
            factory = new ArcherFactory();
            break;
        default :
            factory = new WarriorFactory();
    }
    character = factory.createCharacter();
    character.level();
    character.gather();
    character.fight();
};

當我需要增加一個盜賊職業,就增加一個盜賊Factory,讓它實現createCharacter方法,這樣整個的工廠體系不會有修改的變化,而只是擴展的變化,符合了開閉原則。但是,修改的噩夢落到了客戶端上,這是我的一個困惑,感覺沒有簡化,反而增加了一大堆類和方法,標記一個TODO。

當然,可以發揮JS靈活的特點

CharacterFactory = (function() {
    var roles = {
        Warrior: Warrior,
        Mage: Mage,
        Archer: Archer
    };
    return {
        createCharacter: function(role) {
            var Character = roles[role];
            return Character ? new Character() : new Warrior();
        },
        registerCharacter: function(role, Character) {
            var proto = Character.prototype;
            if (proto.level && proto.gather && proto.fight) {
                roles[role] = Character;
            }
        }
    }
})();

Player.prototype.play = function(role) {
    var character = CharacterFactory.createCharacter(role);
    character.level();
    character.gather();
    character.fight();
};

var Assasin = klass(Character);
CharacterFactory.registerCharacter("Assasin", Assasin);

player.play("Assasin");

 

抽象工廠

讓工廠方法模式里的工廠接口定義一系列的方法來創建一系列的產品,就成了抽象工廠

比如現在角色們需要武器,戰士拿劍,法師拿法杖,這可不能弄錯了

WarriorFactory.prototype.createCharacter = function() {
    return new Warrior();
};
WarriorFactory.prototype.createWeapon = function() {
    return new Sword();
};
MageFactory.prototype.createCharacter = function() {
    return new Mage();
};
MageFactory.prototype.createWeapon = function() {
    return new Wand();
};
ArcherFactory.prototype.createCharacter = function() {
    return new Archer();
};
ArcherFactory.prototype.createWeapon = function() {
    return new Bow();
};

var factory = new WarriorFactory(),
    character = factory.createCharacter(),
    weapon = factory.createWeapon();

我目前對JS應用工廠模式的一些理解,還沒有理解透徹,不足之處,請指正:)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM