您的当前位置:首页正文

JavaScript设计模式(二)

2024-11-07 来源:个人技术集锦

六、Command

var CommandFactory = (function ()
{
    var Command = function ()
    {
    }

    Command.prototype.execute = function ()
    {
        throw new Error("must impl the execute function");
    }

    var createCommand = function (params)
    {
        var C = function (receiver)
        {
            this.temp = null;
            this.receiver = receiver;
        }

        C.prototype = new Command();

        for (var i in params)
        {
            C.prototype[i] = params[i];
        }

        return C;
    }

    return {
        createCommand: createCommand
    };

})();

var Calculator = function ()
{
    var result = 0;
    console.log("result is " + result);

    return {
        add: function (num)
        {
            result += num;
            console.log("result is " + result);
        },

        sub: function (num)
        {
            result -= num;
            console.log("result is " + result);
        }
    }
}

var commandList = [];

var CommandAdd = CommandFactory.createCommand(
    {
        execute: function (num)
        {
            this.temp = num;
            this.receiver.add(num);
            commandList.push(this);
        },

        undo: function ()
        {
            this.receiver.sub(this.temp);
        }
    }
);

var CommandSub = CommandFactory.createCommand(
    {
        execute : function (num)
        {
            this.temp = num;
            this.receiver.sub(num);
            commandList.push(this);
        },

        undo : function ()
        {
            this.receiver.add(this.temp);
        }
    }
);

var calculator = new Calculator();


window.onload = function ()
{
    createButton("1", function ()
    {
        var ca = new CommandAdd(calculator);
        ca.execute(1);
    });

    createButton("2", function ()
    {
        var cs = new CommandSub(calculator);
        cs.execute(1);
    });

    createButton("undo", function ()
    {

        if (commandList.length === 0)
        {
            console.log("can't undo now");
            return;
        }

        var command = commandList.pop();
        command.undo();
    });

}

这里通过一个简单的计算器来演绎命令模式。JavaScript 可以用高阶函数非常方便地实现命令模式。命令模式在 JavaScript 语言中也是一种隐形的模式。为了实现伪继承,这里用了一点小小的技巧,如果“子类”Command没有实现execute方法,运行时将直接抛出异常。

七、Composite

var Folder = function (name)
{
    var parentDir = "/";
    var name = name;
    var files = [];
    var parent = null;

    return {
        add : function (file)
        {
            file.setParentDir(parentDir + name + "/");
            file.setParent(this);
            files.push(file);
        },

        scan : function ()
        {
            console.log(parentDir + name);
            for(var i = 0; i < files.length; i++)
            {
                files[i].scan();
            }
        },

        setParentDir : function (_parentDir)
        {
            parentDir = _parentDir;
        },

        setParent : function (_parent)
        {
            parent = _parent;
        },

        getFiles : function ()
        {
            return files;
        },

        remove : function ()
        {
            if(!parent)
            {
                return;
            }

            var filesInParent = parent.getFiles();

            for(var i = 0; i < filesInParent.length; i++)
            {
                if(filesInParent[i] === this)
                {
                    parent.getFiles().splice(i, 1);
                    break;
                }
            }
        }
    };
};

var File = function (name)
{
    var parentDir = "/";
    var name = name;
    var parent = null;

    return {
        add : function ()
        {
            throw new Error("Unable to add file to the file");
        },

        scan : function ()
        {
            console.log(parentDir + name);
        },

        setParentDir : function (_parentDir)
        {
            parentDir = _parentDir;
        },

        setParent : function (_parent)
        {
            parent = _parent;
        },

        remove : function ()
        {
            if(!parent)
            {
                return;
            }

            var filesInParent = parent.getFiles();

            for(var i = 0; i < filesInParent.length; i++)
            {
                if(filesInParent[i] === this)
                {
                    parent.getFiles().splice(i, 1);
                    break;
                }
            }
        }
    };
};

组合模式最经典的例子莫过于文件和文件夹了,而组合模式的作用就是可以把相同的操作应用在组合对象和单个对象上。在大多数情况下,我们都可以忽略掉组合对象和单个对象之间的差别,从而用一致的方式来处理它们。

八、Template Method

var Template = function (param)
{
    var func = function ()
    {
        console.log("func invoked");
    }

    var func1 = param.func1 || function()
    {
        throw new Error("must deliver func1");
    }

    var func2 = param.func2 || function ()
    {
        throw new Error("must deliver func2");
    }

    var needFunc2 = param.needFunc2 || function ()
    {
        return false;
    }

    var F = function (){};

    F.prototype.invoke = function ()
    {
        func();
        func1();

        if(needFunc2())
        {
            func2();
        }
    }

    return F;
};

var Imp1 = Template({
    func1 : function ()
    {
        console.log("func1 invoked in Imp1");
    }
});

var Imp2 = Template({
    func1 : function ()
    {
        console.log("func1 invoked in Imp2");
    },

    needFunc2 : function ()
    {
        return true;
    },

    func2 : function ()
    {
        console.log("func2 invoked in Imp2");
    }
});

模板方法模式是好莱坞原则的一个典型使用场景,它与好莱坞原则的联系非常明显,当我们用模板方法模式编写一个程序时,就意味着子类放弃了对自己的控制权,而是改为父类通知子类,哪些方法应该在什么时候被调用。作为子类,只负责提供一些设计上的细节。同样用到好莱坞原则的还有观察者模式。在 JavaScript 中,我们很多时候都不需要依样画瓢地去实现一个模版方法模式,高阶函数是更好的选择。

九、Flyweight

var ObjPoolFactory = function (objCreateFn)
{
    var objPool = [];

    return {
        create : function ()
        {
            var obj = objPool.length === 0 ? objCreateFn.apply(this, arguments) : objPool.shift();
            return obj;
        },

        recover : function (obj)
        {
            objPool.push(obj);
        }
    };
}

var iframeFactory = ObjPoolFactory(function ()
{
    var iframe = document.createElement("iframe");
    document.body.appendChild( iframe );

    iframe.onload = function()
    {
        iframe.onload = null; // 防止 iframe 重复加载的 bug
        iframeFactory.recover( iframe ); // iframe 加载完成之后回收节点
    }

    return iframe;
});

var iframe1 = iframeFactory.create();
iframe1.src = 'http://www.baidu.com';

var iframe2 = iframeFactory.create();
iframe2.src = 'http://www.QQ.com';

//这里是false,因为1在自己加载完成后才会把自己的实例放到对象池中
console.log(iframe1 === iframe2);

setTimeout(function()
{
    var iframe3 = iframeFactory.create();
    iframe3.src = 'http://www.163.com';
    //这里是true,此时对象池中已经有了可用的对象,不再会继续创建新的对象
    console.log(iframe3 === iframe1);

}, 3000 );

享元模式是为解决性能问题而生的模式,这跟大部分模式的诞生原因都不一样。在一个存在大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题。这里的对象池就是一个非常典型的应用。

十、Chain of responsibility

/**
 *考虑到实际开发中完全同步的场景很少,这里提供了一个自己写的全callback的例子,同步可以通过callback实现,但反过来就不行了
*/
var ChainPro = function (fn, callback)
{
    var nextProcessor;
    var callback = callback;

    return {

        setNextProcessor : function (processor)
        {
            nextProcessor = processor;
            return nextProcessor;
        },

        request : function ()
        {
            fn.apply(this, arguments);
        },

        next : function ()
        {
            if(!nextProcessor)
            {
                callback("unable to process the request");
                return;
            }

            nextProcessor.request.apply(nextProcessor, arguments);
        },

        getNextProcessor : function ()
        {
            return nextProcessor;
        },

        getCallback : function ()
        {
            return callback;
        }
    };
}

var order500pro = function (orderType, hasPaid)
{
    var self = this;

    if(orderType === 1 && hasPaid === true)
    {
        setTimeout(function ()
        {
            var stock = 100;

            if(stock > 0)
            {
                self.getCallback().call(self, "route1-success");
            }
            else
            {
                self.getCallback.call(self, "route1-failed");
            }

        }, 1000);
    }
    else
    {
        self.next.apply(self.getNextProcessor(), arguments);
    }

};

var order200pro = function (orderType, hasPaid)
{
    var self = this;

    if(orderType === 2 && hasPaid === true)
    {
        setTimeout(function ()
        {
            var stock = 100;

            if(stock > 0)
            {
                self.getCallback().call(self, "route2-success");
            }
            else
            {
                self.getCallback.call(self, "route2-failed")
            }

        }, 1000);
    }
    else
    {
        self.next.apply(self.getNextProcessor(), arguments);
    }

};

var orderNormalPro = function ()
{
    var self = this;

    setTimeout(function ()
    {
        var stock = 0;

        if(stock > 0)
        {
            self.getCallback().call(self, "route3-success");
        }
        else
        {
            self.next.apply(self.getNextProcessor(), arguments);
        }

    }, 1000);
}

var orderNoStockPro = function ()
{
    var self = this;

    self.getCallback().call(self, "route4");
}

var callback = function (result)
{
    console.log("result is " + result);
};

var c1 = ChainPro(order500pro, callback);
var c2 = ChainPro(order200pro, callback);
var c3 = ChainPro(orderNormalPro, callback);
var c4 = ChainPro(orderNoStockPro, callback);

c1.setNextProcessor(c2).setNextProcessor(c3).setNextProcessor(c4);
c1.request(3, false);

责任链模式也可以通过前文所述的AOP来实现,职责链中的节点数量和顺序是可以自由变化的,我们可以在运行时决定链中包含哪些节点。无论是作用域链、原型链,还是 DOM 节点中的事件冒泡,我们都能从中找到职责链模式的影子。职责链模式还可以和组合模式结合在一起,用来连接部件和父部件,或是提高组合对象的效率。

十一、Mediator

function Player(name, teamColor)
{
    this.id = -1;
    this.name = name;
    this.teamColor = teamColor;
    this.state = "alive";
}

Player.prototype.win = function ()
{
    console.log(this.name + " win");
}

Player.prototype.lose = function ()
{
    console.log(this.name + " lose");
}

Player.prototype.die = function ()
{
    this.state = "dead";
}

var playerFactory = (function ()
{
    var id = 0;

    var addPlayer = function (name, teamColor)
    {
        id++;
        var player = new Player(name, teamColor);
        player.id = id;
        playerDirector.receiveMsg("addPlayer", player);
    }

    return {
        addPlayer : addPlayer
    }
})();

var playerDirector = (function ()
{
    var players = {};
    var operations = {};


    operations.showPlayers = function ()
    {
        for(var teamColor in players)
        {
            var arr = players[teamColor];
            for(var i = 0; i < arr.length; i++)
            {
                var player = arr[i];
                console.log(teamColor + ": " + player.name);
            }
        }
    }

    operations.addPlayer = function(player)
    {
        var teamColor = player.teamColor;
        if(!players[teamColor])
        {
            players[teamColor] = [];
        }

        players[teamColor].push(player);
    }

    operations.removePlayer = function (id)
    {
        var player = null;
        for(var teamColor in players)
        {
            var arr = players[teamColor];
            for(var i = 0; i < arr.length; i++)
            {
                player = arr[i];
                if(player.id === id)
                {
                    arr.splice(i, 1);
                    break;
                }
            }
        }

        return player;
    }

    operations.changeTeam = function (id, newTeamColor)
    {
        var player = operations.removePlayer(id);
        if(player)
        {
            player.teamColor = newTeamColor;
            operations.addPlayer(player);
        }
    }

    var receiveMsg = function ()
    {
        var operation = Array.prototype.shift.call(arguments);

        operations[operation].apply(this, arguments);
    }

    return {
        receiveMsg : receiveMsg
    }

})();

playerFactory.addPlayer("amuro", "white");
playerFactory.addPlayer("kamiu", "white");
playerFactory.addPlayer("judo", "white");
playerFactory.addPlayer("char", "red");
playerFactory.addPlayer("haman", "red");

playerDirector.receiveMsg("showPlayers");
playerDirector.receiveMsg("changeTeam", 4, "blue");
playerDirector.receiveMsg("showPlayers");

中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象(类似不和陌生人说话)。如果对象之间的耦合性太高,一个对象发生改变之后,难免会影响到其他的对象,跟“城门失火,殃及池鱼”的道理是一样的。而在中介者模式里,对象之间几乎不知道彼此的存在,它们只能通过中介者对象来互相影响对方。所以,副作用就是因此中介者本身会变得异常复杂,从理论上说复杂一个总比复杂一堆好。但是本文一开始所说的,设计的度才是最难把握的东西,在实际项目中,模块或对象之间有一些依赖关系是很正常的。毕竟我们写程序是为了快速完成项目交付生产,而不是堆砌模式和过度设计。关键就在于如何去衡量对象之间的耦合程度。一般来说,如果对象之间的复杂耦合确实导致调用和维护出现了困难,而且这些耦合度随项目的变化呈指数增长曲线,那我们就可以考虑用中介者模式来重构代码。

十二、Decorator

Function.prototype.before = function (beforeFn)
{
    var self = this;

    return function ()
    {
        if(beforeFn.apply(this, arguments) === false)
        {
            return;
        }
        var ret = self.apply(this, arguments);

        return ret;
    }
}

Function.prototype.after = function (afterFn)
{
    var self = this;

    return function ()
    {
        var ret = self.apply(this, arguments);
        afterFn.apply(this, arguments);

        return ret;
    }
}

var login = function (username, password)
{
    console.log("log with " + username + " and " + " password");
}

var validate = function (username, password)
{
    if(!username)
    {
        console.log("username is empty");
        return false;
    }

    if(!password)
    {
        console.log("password is empty");
        return false;
    }
}

login = login.before(validate);

login("admin", "123");

这里我们又用到了JS的AOP,其实装饰者模式和代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求。
代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。装饰者模式的作用就是为对象动态加入行为。换句话说,代理模式强调一种关系(Proxy 与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确定对象的全部功能时。代理模式通常只有一层代理本体的引用,而装饰者模式经常会形成一条长长的装饰链。

十三、State

var UploadObj = function (fileName)
{
    this.fileName = fileName;
    this.deleteState = new DeleteState(this);
    this.stopState = new StopState(this);
    this.startState = new StartState(this);
    this.pauseState = new PauseState(this);

    this.currentState = this.stopState;
}

UploadObj.prototype.setState = function(newState)
{
    this.currentState = newState;
}

UploadObj.prototype.delete = function ()
{
    console.log("real delete");
}

UploadObj.prototype.start = function ()
{
    console.log("real start");
}

UploadObj.prototype.pause = function ()
{
    console.log("real pause");
}

UploadObj.prototype.onFunction = function ()
{
    this.currentState.functionButtonClick();
}

UploadObj.prototype.onDelete = function ()
{
    this.currentState .deleteButtonClick();
}

//上传,两个button,开始暂停,删除
var stateFactory = (function ()
{
    var State = function (){}

    State.prototype.functionButtonClick = function ()
    {
        throw new Error("must impl the function button click");
    }

    State.prototype.deleteButtonClick = function ()
    {
        throw new Error("must impl the delete button click");
    }

    var createState = function (params)
    {
        var F = function (uploadObj)
        {
            this.uploadObj = uploadObj;
        }

        F.prototype = new State();

        for(var i in params)
        {
            F.prototype[i] = params[i];
        }

        return F;
    }

    return {
        createState : createState
    }

})();

var DeleteState = stateFactory.createState(
    {
        functionButtonClick : function ()
        {
            console.log("illegal operation: the file has been deleted");
        },

        deleteButtonClick : function ()
        {
            console.log("illegal operation: the file has been deleted");
        }
    }
);

var StopState = stateFactory.createState(
    {
        functionButtonClick : function ()
        {
            console.log("stop -> start");
            this.uploadObj.start();
            this.uploadObj.setState(this.uploadObj.startState);
        },

        deleteButtonClick : function ()
        {
            console.log("delete when stop");
            this.uploadObj.delete();
            this.uploadObj.setState(this.uploadObj.deleteState);
        }
    }
);

var StartState = stateFactory.createState(
    {
        functionButtonClick : function ()
        {
            console.log("start -> pause");
            this.uploadObj.pause();
            this.uploadObj.setState(this.uploadObj.pauseState);
        },

        deleteButtonClick : function ()
        {
            console.log("unable to delete file when upload start");
        }
    }
);

var PauseState = stateFactory.createState(
    {
        functionButtonClick : function ()
        {
            console.log("pause -> start");
            this.uploadObj.start();
            this.uploadObj.setState(this.uploadObj.startState);
        },

        deleteButtonClick : function ()
        {
            console.log("delete when pause");
            this.uploadObj.delete();
            this.uploadObj.setState(this.uploadObj.deleteState);
        }
    }
);

var uploadObj = new UploadObj("aBook.pdf");
createButton("function", function ()
{
    uploadObj.onFunction();
});

createButton("delete", function ()
{
    uploadObj.onDelete();
});

以上为标准实现,模拟一个上传文件和删除文件的功能,也可以通过状态机的方式来实现。

var delegate = function (obj, state)
{
    return {
        buttonPressed : function ()
        {
            state.buttonPressed.apply(obj, arguments);
        }
    };
}

var Fsm = {
    offState : {
        buttonPressed : function ()
        {
            console.log("turn on");
            this.currentState = this.onState;
        }
    },

    onState : {
        buttonPressed : function ()
        {
            console.log("turn off");
            this.currentState = this.offState;
        }
    }
};

var LightX = function ()
{
    this.offState = delegate(this, Fsm.offState);
    this.onState = delegate(this, Fsm.onState);

    this.currentState = this.offState;
}

var lightX = new LightX();
lightX.currentState.buttonPressed();

推荐一个JS状态机的开源项目https:// github.com/jakesgordon/
javascript-state-machine。
状态模式和策略模式像一对双胞胎,它们都封装了一系列的算法或者行为,它们的类图看起来几乎一模一样,但在意图上有很大不同,因此它们是两种迥然不同的模式。
策略模式和状态模式的相同点是,它们都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行。它们之间的区别是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,所以客户必须熟知这些策略类的作用,以便客户可以随时主动切换算法;而在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情发生在状态模式内部。对客户来说,并不需要了解这些细节。这正是状态模式的作用所在。

十四、Adapter

var getGuangDongCity = function ()
{
    var guangDongCity = [
        {
            name : "guangzhou",
            id : 11
        },
        {
            name : "shenzhen",
            id : 12
        }
    ];

    return guangDongCity;
}

var newGetGuangDongCity = {
    guangzhou : 11,
    shenzhen : 12,
    zhuhai : 13
};

var cityInfoAdapter = function ()
{
    var resultArr = [];

    for(var i in newGetGuangDongCity)
    {
        var result = {};
        result.name = i;
        result.id = newGetGuangDongCity[i];
        resultArr.push(result);
    }

    return resultArr;
}

var render = function (fn)
{
    console.log("start rendering map of GuangDong");
    var arr = fn();
    for(var i = 0; i < arr.length; i++)
    {
        var element = arr[i];
        for(var j in element)
        {
            console.log(j + " : " + element[j]);
        }
    }
}

render(cityInfoAdapter);

适配器模式比较简单,它主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协同作用。

最后再唠叨一下设计模式六大原则:
单一职责,里式替换,依赖倒置,接口隔离,迪米特,开闭

显示全文