2018年11月

其中有些设计模式可能在实际中没用用到过,有些用到了可能自己也不知道。O(∩_∩)O

单例模式

一个类仅有一个实例。
var S = function(){
    this._instance = null;
}

S.prototype.getInstance = function(){
    if (!this._instance) return new S();
    return this._instance;
}

场景:一个登录框、一个数据库连接实例

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
var S = function( salary ){
  return salary * 4;
};
var A = function( salary ){
  return salary * 3;
};
var B = function( salary ){
  return salary * 2;
};
var calculateBonus = function( func, salary ){
  return func( salary );
};
calculateBonus( S, 10000 ); // 输出:40000

场景:实现不同的动画(如仅仅因为动画计算方法不同)、表单提交验证(有很多条验证的规则)。

简单地说就是把【算法】部分提取出来。

代理模式

为其他对象提供一种代理以控制对这个对象的访问。

分为两种代理:1.保护代理;对被代理的对象进行一些过滤等操作。2.虚拟代理。虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。

形象的描述一下,书上的例子:

A在追求C,请求C的好朋友B去送东西。

保护代理:B如果在收到A的东西里面发现只有鲜花一类没用的东西,就不送给C了。如果B发现A送的是宝马车,那么就把东西转交给C。

虚拟代理:B在收到A的东西后,不着急转交给C。在C的心情好的时候交给他,这样A追求成功的机率会大一些。

体现了单一职责原则;当然可以把B的功能写在A里面,但是这样在修改的时候可能会感到困难。

场景:请求代理、图片懒加载

// 请求代理,在不同环境下请求不同的host。保护代理
var proxyRequest = function(url){
  if (environment === 'development'){
    url = 'http://localhost' + url;
  }
  if (environment === 'production'){
    url = 'http://www.xxx.com' + url;
  }
  return fetch(url);
}
proxyRequest('/api/')

// 图片懒加载。虚拟代理
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
  return {
  setSrc: function( src ){
    imgNode.src = src;
    }
  }
})();

var proxyImage = (function(){
  var img = new Image;
    img.onload = function(){
      myImage.setSrc( this.src );
    }
    return {
      setSrc: function( src ){
      myImage.setSrc( 'file:///C:/Users/svenzeng/Desktop/loading.gif' );
      img.src = src;
    }
  }
})();

proxyImage.setSrc( 'http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

发布-订阅模式

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

在JS中非常常见:Ajax回调、事件监听...

组合模式

组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。

实例:文件扫描器

/******************************* Folder ******************************/
var Folder = function( name ){
  this.name = name;
  this.files = [];
};
Folder.prototype.add = function( file ){
  this.files.push( file );
};
Folder.prototype.scan = function(){
  console.log( '开始扫描文件夹: ' + this.name );
  for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
    file.scan();
  }
};
/******************************* File ******************************/
var File = function( name ){
  this.name = name;
};
File.prototype.add = function(){
  throw new Error( '文件下面不能再添加文件' );
};
File.prototype.scan = function(){
  console.log( '开始扫描文件: ' + this.name );
};

var folder = new Folder( '学习资料' );
var folder1 = new Folder( 'JavaScript' );
var folder2 = new Folder ( 'jQuery' );
var file1 = new File( 'JavaScript 设计模式与开发实践' );
var file2 = new File( '精通 jQuery' );
var file3 = new File( '重构与模式' )
folder1.add( file1 );
folder2.add( file2 );
folder.add( folder1 );
folder.add( folder2 );
folder.add( file3 );
folder.sacn()

享元模式

享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

享元模式要求将对象的属性划分为内部状态与外部状态(状态在这里通常指属性)。

  • 内部状态储存于对象内部。
  • 内部状态可以被一些对象共享。
  • 内部状态独立于具体的场景,通常不会改变。
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。

通俗讲就是把需要大量的实例的共同点(内部状态)提取出来,只实例化一个,其他属性(外部状态)由管理器去操作、管理。

书中的例子是有一个上传2000个文件的操作,有些是通过Flash上传,有些通过表单上传。new了2000个Upload实例。但是他们唯一不同的是其中uploadType不同,所以可以只new 2个Upload实例就行了,分别代表用Flash上传和表单上传,其他属性,也可以说是外部状态(文件名、文件大小等)由外部的管理器去修改、操作。

适配器模式

适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。

常见的场景如一些ORM框架,只需要填写简单的配置,在不同的环境下使用不同的数据库。Session的配置,模板引擎的配置等。

ThinkJS框架的Adapter文档中有写到

前端方面我觉得pollyfill应该也算是适配器模式吧。

function AnimationAdapter(){
  if (!window.requestAnimationFrame){
    return function(fn){
      return setTimeout(fn, 17);
    }
  }
  return window.requestAnimationFrame;
}
let fn = AnimationAdapter()

装饰器模式

装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

装饰器在Python、ES6中都有,其本质就是一个函数。只是通过一个语法糖简化了传递函数的过程。可以将装饰器就看作一个函数A,参入的参数是函数B,对传入的函数B进行修改后返回一个新的函数C。

但是在JS中装饰器不能用于一个函数。

@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;

和适配器模式的区别:装饰器模式一般给函数、对象增加功能,可能增加很多个功能。而适配器通常只包装一次。

一般用于AOP

中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。

最少知识原则,减少对象之间的联系。