模式有三种:Architectural Pattern、Design Pattern、Coding Pattern,即:框架模式、设计模式、编程模式。本文主要讲解javascript中的设计模式,好的设计模式能够提高代码的重用性,可读性,使代码更容易的维护和扩展。本文适合有一点javascript基础,对javascript的概念有所了解。
一、单例模式:
单例模式是javascript中最常用的模式,它是将自己的代码放在一个命名空间下,这样的好处是可以减少使用全局变量,在多人协同开发时也能避免命名冲突等问题。这样的好处是维护起来非常方便,如下例:
1 var m = { 2 name: 'dog', 3 action: function() { 4 console.log(this.name); 5 } 6 }; 7 m.action();//调用
或者
1 var dog = function() { 2 this.name = 'dog'; 3 this.action = function() { 4 return console.log(this.name); 5 }; 6 action(); 7 }; 8 dog(); //调用
工厂模式:
工厂模式就是将对象的方法创建交给外部对象,这样的好处就是解决了对象之间的相互影响、即耦合,避免了使用new来实例化对象,有助于创建模块化的代码,维护起来也方便。 工厂模式分为简单工厂模式和抽象工厂模式,下面介绍简单工厂模式:
1 var m = {}; 2 m.action = function() { 3 console.log('dog'); 4 }; 5 var demo = function() { 6 m.action(); 7 }; 8 demo()//调用
抽象工厂模式先设计一个抽象类,这个类不能被实例化,只能用来派生子类,最后通过对子类的扩展实现工厂方法。如下:
1 var f = function() {}; 2 f.prototype = { 3 c: function() { 4 throw new Error('can\'t use this method');//如果调用此方法会报错,因为它是用来派生子类不能实例化 5 } 6 }; 7 var e = function() { 8 f.call(this); 9 } 10 e.prototype = new f(); 11 e.prototype.constructor = e; 12 e.prototype.c = function() { 13 console.log('this method is redefine'); 14 } 15 // 调用 16 var demo = new e(); 17 demo.c();
桥接模式:
桥接模式是将抽象与实现隔离,一遍二者独立变化。在设计一个javascript API的时候,它可以用来弱化类和对象之间的耦合。它还可以用来把多个类联接在一起。例如:
1 var class1 = function(a,b,c) { 2 this.a = a; 3 this.b = b; 4 this.c = c; 5 }; 6 var class2 = function(d) { 7 this.d = d; 8 }; 9 var demo = function(a,b,c,d) { 10 this.one = new Class1(a,b,c); 11 this.two = new Class2(d); 12 };
组合模式:
组合模式可以用一条命令在多个对象上激发复杂的或递归的行为。好处是可以用同样的发放处理对象的集合与其中的特定子对象,也可以用来把一批子对象组织成树形结构,并且使整个树都可被遍历。如下:
1 // DynamicGallery Class 2 var DynamicGallery =function (id) { // 实现Composite,GalleryItem组合对象类 3 this.children = []; 4 this.element = document.createElement('div'); 5 this.element.id = id; 6 this.element.className ='dynamic-gallery'; 7 } 8 DynamicGallery.prototype = { 9 // 实现Composite组合对象接口 10 add: function (child) { 11 this.children.push(child); 12 this.element.appendChild(child.getElement()); 13 }, 14 remove: function (child) { 15 for (var node, i =0; node =this.getChild(i); i++) { 16 if (node == child) { 17 this.children.splice(i, 1); 18 break; 19 } 20 } 21 this.element.removeChild(child.getElement()); 22 }, 23 getChild: function (i) { 24 returnthis.children[i]; 25 }, 26 // 实现DynamicGallery组合对象接口 27 hide: function () { 28 for (var node, i =0; node =this.getChild(i); i++) { 29 node.hide(); 30 } 31 this.element.style.display ='none'; 32 }, 33 show: function () { 34 this.element.style.display ='block'; 35 for (var node, i =0; node = getChild(i); i++) { 36 node.show(); 37 } 38 }, 39 // 帮助方法 40 getElement: function () { 41 returnthis.element; 42 } 43 }
1 var GalleryImage =function (src) { // 实现Composite和GalleryItem组合对象中所定义的方法 2 this.element = document.createElement('img'); 3 this.element.className ='gallery-image'; 4 this.element.src = src; 5 } 6 GalleryImage.prototype = { 7 // 实现Composite接口 8 // 这些是叶结点,所以我们不用实现这些方法,我们只需要定义即可 9 add: function () { }, 10 remove: function () { }, 11 getChild: function () { }, 12 // 实现GalleryItem接口 13 hide: function () { 14 this.element.style.display ='none'; 15 }, 16 show: function () { 17 this.element.style.display =''; 18 }, 19 // 帮助方法 20 getElement: function () { 21 returnthis.element; 22 } 23 }
1 var topGallery =new DynamicGallery('top-gallery'); 2 topGallery.add(new GalleryImage('/img/image-1.jpg')); 3 topGallery.add(new GalleryImage('/img/image-2.jpg')); 4 topGallery.add(new GalleryImage('/img/image-3.jpg')); 5 var vacationPhotos =new DyamicGallery('vacation-photos'); 6 for(var i =0, i <30; i++){ 7 vacationPhotos.add(new GalleryImage('/img/vac/image-'+ i +'.jpg')); 8 } 9 topGallery.add(vacationPhotos); 10 topGallery.show(); 11 vacationPhotos.hide();
门面模式:
门面模式常常是开发人员最亲密的朋友,他几乎是所有javascript库的核心原则。门面模式有两个作用:一是简化类的接口;二是消除类与使用它的客户代码之间的耦合。示例如下:
1 function a() { 2 3 } 4 function b() { 5 6 } 7 function ab() { 8 a(); 9 b(); 10 }
适配器模式:
适配器模式可以用来在现有接口和不兼容的类之间进行适配。从表面上看,适配器模式很像门面模式,都对别的对象进行包装并改变其呈现的接口。二者的区别在与它们如何改变接口,门面元素展现的是一个简化的接口,它并不提供额外的选择,而且有时为了方便完成常见任务它还会做出一些假定。而适配器则要把一个接口转换为另一个接口,它并不会过滤某些能力,也不会简化接口。
1 var str = { 2 a: 'a', 3 b: 'b', 4 c: 'c' 5 }; 6 function i(s1,s2,s3) { 7 console.log(s1 + ',' + s2 + ',' + s3); 8 } 9 function demo(o) { 10 i(o.a,o.b,o.c); 11 }
装饰者模式:
装饰者模式可用来透明地把对象包装在具有同样接口的另一个对象之中,装饰者可以用于为对象添加功能,可以用来代替大量子类。装饰者模式和组合模式有很多共同点,它们都与所包装的对象实现统一的接口并且会把任何方法条用传递给这些对象。可是组合模式用于把众多子对象组织为一个整体,而装饰者模式用于在不修改现有对象或从派生子类的前提下为其添加方法。如下:
1 var m = {}; 2 m.child = {}; 3 m.child.one = function() {}; 4 m.child.two = function() {};
享元模式:
享元模式最适合于解决因创建大量类似对象而累及性能的问题。通过把大量独立对象转化为少量共享对象,可以降低运行web应用程序所需的资源数量。
javascript设计模式中的示例:
1 //汽车登记示例 2 var Car =function(make,model,year,owner,tag,renewDate){ 3 this.make=make; 4 this.model=model; 5 this.year=year; 6 this.owner=owner; 7 this.tag=tag; 8 this.renewDate=renewDate; 9 } 10 Car.prototype = { 11 getMake:function(){ 12 returnthis.make; 13 }, 14 getModel:function(){ 15 returnthis.model; 16 }, 17 getYear:function(){ 18 returnthis.year; 19 }, 20 transferOwner:function(owner,tag,renewDate){ 21 this.owner=owner; 22 this.tag=tag; 23 this.renewDate=renewDate; 24 }, 25 renewRegistration:function(renewDate){ 26 this.renewDate=renewDate; 27 } 28 } 29 //数据量小到没多大的影响,数据量大的时候对计算机内存会产生压力,下面介绍享元模式优化后 30 //包含核心数据的Car类 31 var Car=function(make,model,year){ 32 this.make=make; 33 this.model=model; 34 this.year=year; 35 } 36 Car.prototype={ 37 getMake:function(){ 38 returnthis.make; 39 }, 40 getModel:function(){ 41 returnthis.model; 42 }, 43 getYear:function(){ 44 returnthis.year; 45 } 46 } 47 //中间对象,用来实例化Car类 48 var CarFactory=(function(){ 49 var createdCars = {}; 50 return { 51 createCar:function(make,model,year){ 52 var car=createdCars[make+"-"+model+"-"+year]; 53 return car ? car : createdCars[make +'-'+ model +'-'+ year] =(new Car(make,model,year)); 54 } 55 } 56 })(); 57 //数据工厂,用来处理Car的实例化和整合附加数据 58 var CarRecordManager = (function() { 59 var carRecordDatabase = {}; 60 return { 61 addCarRecord:function(make,model,year,owner,tag,renewDate){ 62 var car = CarFactory.createCar(make, model, year); 63 carRecordDatabase[tag]={ 64 owner:owner, 65 tag:tag, 66 renewDate:renewDate, 67 car:car 68 } 69 }, 70 transferOwnership:function(tag, newOwner, newTag, newRenewDate){ 71 var record=carRecordDatabase[tag]; 72 record.owner = newOwner; 73 record.tag = newTag; 74 record.renewDate = newRenewDate; 75 }, 76 renewRegistration:function(tag,newRenewDate){ 77 carRecordDatabase[tag].renewDate=newRenewDate; 78 }, 79 getCarInfo:function(tag){ 80 return carRecordDatabase[tag]; 81 } 82 } 83 })();
代理模式:
代理是一个对象,它可以用来控制对另一个对象的访问。它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象。代理模式适合处理实例化比较费时的本体,也适合处理那些需要较长时间才能把数据载入用户界面的类。
javascript设计模式中的示例:
1 var Publication =new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor', 'setAuthor', 'display']); 2 var Book =function(isbn, title, author) { 3 //... 4 } 5 // implements Publication 6 implements(Book,Publication); 7 8 /* Library interface. */ 9 var Library =new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']); 10 11 /* PublicLibrary class. */ 12 var PublicLibrary =function(books) { 13 //... 14 }; 15 // implements Library 16 implements(PublicLibrary,Library); 17 18 PublicLibrary.prototype = { 19 findBooks: function(searchString) { 20 //... 21 }, 22 checkoutBook: function(book) { 23 //... 24 }, 25 returnBook: function(book) { 26 //... 27 } 28 }; 29 30 /* PublicLibraryProxy class, a useless proxy. */ 31 var PublicLibraryProxy =function(catalog) { 32 this.library =new PublicLibrary(catalog); 33 }; 34 // implements Library 35 implements(PublicLibraryProxy,Library); 36 37 PublicLibraryProxy.prototype = { 38 findBooks: function(searchString) { 39 returnthis.library.findBooks(searchString); 40 }, 41 checkoutBook: function(book) { 42 returnthis.library.checkoutBook(book); 43 }, 44 returnBook: function(book) { 45 returnthis.library.returnBook(book); 46 } 47 };
观察者模式:
观察者模式是一种管理人与其任务之间的关系的得力工具。观察者模式中存在两个角色:观察者和被观察者。这种模式的好处是你可以对程序中某个对象的状态进行观察,并且在其发生改变时能够得到通知。
1 var f1 =function(){ 2 //code 3 } 4 var f2 =function(){ 5 //code 6 } 7 addEvent(element,'click',f1); 8 addEvent(element,'click',f2) 9 10 11 element.onclick = f1; 12 element.onclick = f2;
命令模式:
命令模式可以用来对方法调用进行参数化处理和传送,经这样处理过的方法调用可以在任何需要的时候执行。好处是可以用来消除调用操作的对象和实现操作的对象之间的耦合,使对象间的互动方式更高的模块化。这为各种具体的类更换带来了极大的灵活性。
1 car Calculator={ 2 add:function(x,y){ 3 return x+y; 4 }, 5 substract:function(x,y){ 6 return x-y; 7 }, 8 multiply:function(x,y){ 9 return x*y; 10 }, 11 divide:function(x,y){ 12 return x/y; 13 } 14 } 15 Calculator.calc =function(command){ 16 return Calculator[command.type](command.op1,command.opd2) 17 }; 18 Calculator.calc({type:'add',op1:1,op2:1}); 19 Calculator.calc({type:'substract',op1:5,op2:2}); 20 Calculator.calc({type:'multiply',op1:5,op2:2}); 21 Calculator.calc({type:'divide',op1:8,op2:4});
职责链模式:
职责链模式是通过实现一个由隐式地请求进行处理对象组成的链而做到的。可以用来消除请求的发送者和接收者之间的耦合
javascript内部就使用了这种模式来处理事件捕获和冒泡的问题。
职责链由多个不同类型的对象组成:发送者是发出请求的对象,而接收者则是接收请求并且对其进行处理或传递的对象,请求本身有时也是一个对象,它封装着与操作有关的所有数据。其典型的流程大致是:
- 发送者知道链中第一个接收者,它向这个接收者发出请求。
- 每一个接收者都对请求进行分析,然后要么处理它,要么将其往下传。
- 每一个接收者知道的其他对象只有一个,即它在链中的下家。
- 如果没有任何接收者处理请求,那么请求将从链上离开,不同的实现对此也有不同的反应,一般会抛出一个错误。
小结:
每种模式都有自己的优点,选择一种适合自己业务的模式非常重要,能够提高代码的可读性、可维护性等等,希望本文能够对你有所帮助。
本文参考电子书《javascript设计模式》