GoF合做出版的《设计模式》这本书提供了许多有关与面向对象软件设计中常见问题的解决方案。这些模式已经出现了至关长的一段时间,已经被证实在许多状况下都很是有用。
前端
一个特定类仅有一个实例。这意味着当您第二次使用同一个类建立新对象的时候,应该获得与第一次所建立对象彻底相同对象。算法
使用对象字面量建立一个简单的对象也是一个单体的例子,由于在JavaScript中没有类,只有对象。当您建立一个新对象时,实际上没有其余对象与其相似,所以新对象已是单体了。编程
var obj = { myprop: 'my value' };
JavaScript没有类,可是能够经过new语法使用构造函数来建立对象,有时有可能须要使用这种语法的单体实现。这种思想在于当使用同一个构造函数以new操做符来建立多个对象时,应该仅得到指向彻底相同的对象的新指针。设计模式
在构造函数的静态属性中缓存该实例。您可使用相似Universe.instance的属性并将实例缓存在该属性中。这中方案的缺点在于instance属性是公开可访问的属性,在外部代码中可能会修改该属性。数组
function Universe() { if (typeof Universe.instance === 'object') { return Universe.instance; } this.start_time = 0; Universe.instance = this; } // 测试 var uni = new Universe(); var uni2 = new Universe(); uni === uni2; // true
能够将该实例包装在闭包中。这样能够保证该实例的私有性。其代价是带来了额外的闭包开销。浏览器
function Universe() { var instance = this; this.start_time = 0; Universe = function () { return this; }; }
若是须要使原型和构造函数指针按照预期的那样运行,能够经过作一些调整来实现这个目标:缓存
function Universe() { var instance; Universe = function Universe() { return instance; }; Universe.prototype = this; instance = new Universe(); instance.constructor = Universe; instance.start_time = 0; return instance; }
另外一种解决方案也是将构造函数和实例包装在即时函数中。安全
var Universe; (function () { var instance; Universe = function Universe() { if (instance) { return instance; } instance = this; this.start_time = 0; }; }());
设计工厂模式的目的是为了建立对象。它一般在类或者类的静态方法中实现,具备下列目标:数据结构
经过工厂方法(或类)建立的对象在设计上都继承了相同的父对象这个思想,它们都是实现专门功能的特定子类。有时候公共父类是一个包含了工厂方法的同一个类。闭包
下面是工厂模式的实现示例
function CarMaker() {} CarMaker.prototype.drive = function () { return this.doors; }; CarMaker.factory = function (type) { var constr = type, newcar; if (typeof CarMaker[constr] !== 'function') { throw { name: "Error", message: constr + " not exist" }; } if (typeof CarMaker[constr].prototype.drive !== 'function') { CarMaker[constr].prototype = new CarMaker(); } newcar = new CarMaker[constr](); return newcar; }; CarMarker.Compact = function () { this.doors = 4; }; CarMarker.Convertible = function () { this.doors = 2; }; CarMarker.SUV = function () { this.doors = 24; };
var o = new Object(), n = new Object(), s = Object('1'), b = Object(true); o.constructor === Object; n.constructor === Number; s.constructor === String; b.constructor === Boolean; // 都为true
在迭代器模式中,一般有一个包含某种数据集合的对象。该数据可能存储在一个复杂数据结构内部,而要提供一种简单的方法可以访问数据结构中每一个元素。对象的消费者并不须要知道如何组织数据,全部须要作的就是取出单个数据进行工做。
在迭代器模式中,对象须要提供一个next()方法。依次调用next()必须返回下一个连续的元素。固然,在特定数据结构中,"下一个"所表明的意义是由您来决定的。
示例
var agg = (function () { var index = 0, data = [1, 2, 3, 4, 5], length = data.length; return { next: function () { var element; if (!this.hasNext()) { return null; } element = data[index]; index = index + 2; return element; }, hasNext: function () { return index < length; } }; }());
在装饰者模式中,能够在运行时动态添加附加功能到对象中。装饰者模式的一个比较方便的特征在于其预期行为的可定制和可配置特性。能够从仅具备一些基本功能的普通对象开始,而后从可用装饰资源池中选择须要用于加强普通对象的那些功能,而且按照顺序进行装饰,尤为是当装饰顺序很重要的时候。
function Sale(price) { this.price = price || 100; } Sale.prototype.getPrice = function () { return this.price; }; Sale.decorators.fedtax = { getPrice: function () { var price = this.uber.getPrice(); price += price * 5 / 100; return price; } }; Sale.decorators.quebec = { getPrice: function () { var price = this.uber.getPrice(); price += price * 7.5 / 100; return price; } }; Sale.prototype.decorate = function(decorator) { var F = function () {}, overrides = this.constructor.decorators[decorator], i, newobj; F.prototype = this; newobj = new F(); newobj.uber = F.prototype; for (i in overrides) { if (overrides.hasOwnProperty(i)) { newobj[i] = overrides[i]; } } return newobj; }; // 测试 var sale = new Sale(100); sale = sale.decorate('fedtax'); sale = sale.decorate('quebec'); sale.getPrice();
function Sale(price) { this.price = (price > 0) || 100; this.decorators_list = []; } Sale.decorators = {}; Sale.decorators.fedtax = { getPrice: function (price) { return price + price * 5 / 100; } }; Sale.decorators.quebec = { getPrice: function (price) { return price + price * 7.5 / 100; } }; Sale.prototype.decorate = function (decorator) { this.decorators_list.push(decorator); }; Sale.prototype.getPrice = function () { var price = this.price, i, max = this.decorators_list.length, name; for (i = 0; i < max; i += 1) { name = this.decorators_list[i]; price = Sale.decorators[name].getPrice(price); } return price; } // 测试 var sale = new Sale(100); sale.decorate('fedtax'); sale.decorate('quebec'); sale.getPrice();
策略模式支持您在运行时选择算法。代码的客户端可使用同一个接口来工做,可是它却根据客户正在试图执行任务的上下文,从多个算法中选择用于处理特定任务的算法。
var validator = { types: {}, messages: [], config: {}, validate: function (data) { var i, msg, type, checker, result_ok; this.messages = []; for (i in data) { if (data.hasOwnProperty(i)) { type = this.config[i]; checker = this.types[type]; if (!type) { continue; } if (!checker) { throw { name: 'ValidationError', message: 'No handler to validate type ' + type; }; } result_ok = checker.validate(data[i]); if (!result_ok) { msg = "Invalid value for *" + i + "*, " + checker.instructions; this.messages.push(msg); } } } return this.hasErrors(); }, hasErrors: function () { return this.message.length !== 0; } }; validator.types.isNonEmpty = { validate: function (value) { return value !== ""; }, instructions: "this value cannot be empty" }; validator.types.isNumber = { validate: function (value) { return !isNaN(value); }, instructions: "this value can only be a valid number, e.g. 1, 3.14 or 2010" }; // 测试 var data = { first_name: "Super", age: "unknown", }; validator.config = { first_name: 'isNonEmpty', age: 'isNumber', }; validator.validate(data); if (validator.hasErrors()) { console.log(validator.message.join("\n")); }
外观模式是一种简单的模式,它为对象提供了一个可供选择的接口。这是一种很是好的设计实践,可保持方法的简洁性而且不会使它们处理过多的工做。若是原来有许多接受多个参数的uber方法,相比而言,按照本实现方法,最终将会建立更多数量的方法。有时候,两个或更多的方法可能广泛的被一块儿调用。在这样的状况下,建立另外一个方法以包装重复的方法调用是很是有意义的。
外观模式很是适合于浏览器脚本处理,据此可将浏览器之间的差别隐藏在外观以后。
var myevent = { stop: function (e) { if (typeof e.preventDefault === 'function') { e.preventDefault(); } if (typeof e.stopPropagation === 'function') { e.stopPropagation(); } if (typeof e.returnValue === 'boolean') { e.returnValue = false; } if (typeof e.cancelBubble === 'boolean') { typeof e.cancelBubble = true; } } };
在代理设计模式中,一个对象充当另外一个对象的接口。代理介于对象的客户端和对象自己之间,而且对该对象的访问进行保护。
使用这种模式的其中一个例子是咱们能够称为延迟初始化的方法,代理接收初始化请求,可是直到该本体对象明确的将被实际使用以前,代理从不会将该请求传递给本体对象。
经过代理合并多个http请求以提升性能
缓存代理
应用程序,不管其大小,都是由一些单个的对象所组成。全部这些对象须要一种方式来实现相互通讯,而这种通讯方式在必定程度上不下降可维护性,也不损害那种安全的改变部分应用程序而不会破坏其他部分的能力。随着应用程序的增加,将添加愈来愈多的对象。而后在代码重构期间,对象将被删除或从新整理。当对象相互知道太多信息而且直接通讯(调用对方的方法并改变属性)时,这将致使产生不良的紧耦合问题。
中介者模式缓解了该问题并促进造成松耦合,在这种模式中,独立的对象之间并不直接通讯,而是经过mediator对象。当其中一个colleague对象改变状态之后,它将会通知该mediator,而mediator将会把该变化传达到任意其余应该知道此变化的colleague对象。
function Player(name) { this.points = 0; this.name = name; } Player.prototype.play = function () { this.points += 1; mediator.played(); } var scoreboard = { element: document.getElementById('results'); update: function (score) { var i, msg = ''; for (i in score) { if (score.hasOwnProperty(i)) { msg += i + ': ' + score[i] + ' | ' } } this.element.innerHTML = msg; } }; var mediator = { players: {}, setup: function () { var players = this.players; players.home = new Player('Home'); players.guest = new Player('Guest'); }, played: function () { var players = this.players, score = { Home: players.home.points, Guest: players.guest.points }; scoreboard.update(score); }, keypress: function (e) { e = e || window.event; if (e.whitch === 49) { mediator.players.home.play(); return; } if (e.whitch === 48) { mediator.players.guest.play(); return; } } }; // 测试 mediator.setup(); window.onkeypress = mediator.keypress; setTimeout(function () { window.onkeypress = null; alert('Game over!'); }, 30000);
观察者模式普遍应用于客户端JavaScript编程中。全部的浏览器事件是该模式的例子。它也叫订阅/发布模式。
设计这种模式背后的主要动机是促进造成松耦合。在这种模式中,并非一个对象调用另外一个对象的方法,而是一个对象订阅另外一个对象的特定活动并在状态改变后得到通知。订阅者也称之为观察者,而被观察者的对象称为发布者或者主题。当发生了一个重要的事件时,发布者将会通知(调用)全部订阅者而且可能常常以事件对象的形式传递消息。
咱们要实现一个功能:发布者paper,天天出版报纸以及月刊杂志,订阅者joe被通知发生的新闻。
下面是通用发布者功能的一个实现示例
var publisher = { subscribers: { any: [] // 事件类型:订阅者(subscribers) }, // 将订阅者添加到subscribers数组 subscribe: function (fn, type) { type = type || 'any'; if (typeof this.subscribers[type] === 'undefined') { this.subscribers[type] = []; } this.subscribers[type].push(fn); }, // 从订阅者数组subscribers中删除订阅者 unsubscribe: function (fn, type) { this.visitSubscribers('unsubscribe', fn, type); }, // 循环遍历subscribers中的每一个元素,而且调用他们注册时提供的方法 publish: function (publication, type) { this.visitSubscribers('publish', publication, type); }, visitSubscribers: function (action, arg, type) { var pubtype = type || 'any', subscribers = this.subscribers[pubtype], i, max = subscribers.length; for (i = 0; i < max; i += 1) { if (action === 'publish') { subscribers[i](arg); } else { if (subscribers[i] === arg) { subscribers.splice(i, 1); } } } } };
将普通对象转换成发布者对象
function makePublisher(o) { var i; for (i in publisher) { if (publisher.hasOwnProperty(i) && typeof publisher[i] === 'function') { o[i] = publisher[i]; } } o.subscribers = { any: [] }; }
功能实现:
paper对象
var paper = { daily: function () { this.publish("big news totay"); }, monthly: function () { this.publish("interesting analysis", "monthly"); } };
将paper构形成一个发布者
makePublisher(paper);
订阅者对象joe
var joe = { drinkCoffee: function (paper) { console.log('Just read ' + paper); }, sundayPreNap: function (monthly) { console.log('About to fall asleep reading this' + monthly); } };
joe向paper订阅
paper.subscribe(joe.drinkCoffee); paper.subscribe(joe.sundayPreNap, 'monthly');
触发一些事件
paper.daily(); paper.monthly();
打印结果
Just read big news totay About to fall asleep reading this interesting analysis
代码好的部分在于,paper对象没有硬编码joe,joe对象也没有硬编码paper。此外,代码中也没有那些知道一切的中介者对象。参与对象是松耦合的,咱们能够向paper添加更多的订阅者而不须要修改这些对象。
让咱们将例子更进一步扩展而且让joe成为发布者:
makePublisher(joe); joe.tweet = function (msg) { this.publish(msg); };
选择paper的公关部门须要读取读者的tweet,而且订阅joe的信息,那么须要提供方法readTweets():
paper.readTweets = function (tweet) { console.log('Call big meeting! Someone ' + tweet); }; joe.subscribe(paper.readTweets);
执行
joe.tweet('hated the paper today');
打印
Call big meeting! Someone hated the paper today