javascript 常见设计模式

一、单体模式

单体模式是最常见的设计模式,总共有四种:1、简单单体,2、闭包单体, 3、惰性单体, 4、分支单体。

1、简单单体

var singleton = {
  attr1: true,
  attr2: 10,
  method1: function () {},
  method2: function () {}
}

补充: 利用简单单体我们可以创建自己的命名空间,这在大型项目中一般会用到,如下:

var TRHL = {}; // 我自己的命名空间
TRHL.singleton = {
  attr1: true,
  attr2: 10,
  method1: function () {},
  method2: function () {}
}

2、闭包单体

闭包单体比简单单体安全性更好,有自己的作用域,并且可以在内部定义自己的私有变量

var TRHL = {};
TRHL.singleton = (function () {
  var _a1= true;
  var _a2 = 10;
  var _f1 = function () {alert('f1')};
  var _f2 = function () {alert('f2')};
  return {
    attr1: _a1,
    attr2: _a2,
    method1: function () {return _f1()},
    method2: function () {return _f2()},
  }
})()

3、惰性单体

闭包单体有一个问题,就是每次调用都会加载所有属性,比较耗费资源,惰性单体可以解决这个问题:

var TRHL = {};
TRHL.singleton = (function () {
  var _uniqInstance; // 定义一个变量,初始为undefined,用于控制是否加载
  var _init = function () {
    var _a1 = true;
    var _a2 = 10;
    var _fn1 = function () { alert('fn1') };
    var _fn2 = function () { alert('fn2') };
    return {
	  attr1: _a1,
	  attr2: _a2,
	  method1: function () { _fn1() };
	  method2: function () { _fn2() };
	}
  }
  
  return {
	getInstance: function () {
	  if (!_uniqInstance) {
	  _uniqInstance = _init();
      }
      return _uniqInstance;
	}
  }
})()

TRHL.singleton.getInstance().method1()

惰性单体是底层框架比较常用的设计模式

4、分支单体

分支单体是根据条件来决定加载哪一块属性,一般用来判断浏览器类型

var TRHL = {};
TRHL.singleton = (function () {
  var def = true;
  var obj1 = {
	attr1: '火狐浏览器'
  }
  var obj2 = {
	attr2: 'IE浏览器'
  }
  return {
	def ? obj1 : obj2;
  }
})()

二、函数链式调用

1、简单的链式调用

function Dog () {
  this.eat = function () {
	alert('dog is eating');
	return this; // 返回当前调用对象
  }
  this.run = function () {
	alert('dog is running');
	return this; // 返回当前调用对象
  }
  this.sleep = function () {
	alert('dog is sleeping');
	return this; // 返回当前调用对象
  }
}
var d1 = new Dog();
d1.eat().run().sleep();

2、简单模拟jquery底层链式调用

(function (window, undefined) {
  // 创建私有类_$
  function _$ (arguments) {
    // 简单实现一个id选择器
    var idSelector;
    this.dom;
    if (idSelector.test(arguments[0])) {
	  this.dom = document.getElementById(arguments[0].substring(1));
    } else {
	  throw new Error('arguments is error')
	}
  };
  // 在Function类中拓展一个可以实现链式编程的方法
  Function.prototype.method = function (methodName, fn) {
	this.prototype[methodName] = fn
  }
  window.$ = _$; // 将私有类的地址复制给window.$
  // 添加方法
  _$.prototype.addEvent (type, fn) {
	this.dom.addEventListener(type, fn);
	return this; // 链式调用
  };
  _$.prototype.css (prop, val) {
	this.dom.style[prop] = val;
	return this;
  };
  _$.onReady = function (fn) {
    // 真正在内存中为window.$开辟一个对象空间
	window.$ = function () {
	  return new _$;
	}
	// 执行传进来的函数
	fn();
	// 实现链式编程
	_$.method('addEvent', function () {}).method('css', function () {});
  }
})(window)

$.onReady(function () {
  $('#input')
  .addEvent('click', function () {
    alert('点击我');
  })
  .css('backgroundColor', 'red');
})

三、工厂模式

工厂模式的核心就是生产对象、实现解耦,使我们的程序更加面向对象、多态化

1、简单工厂

写代码前的思路:我要建立一个卖汽车的商店,它可以出售各种型号的汽车。好了,这只是一个粗略的想法,下一步,我们要让我们的程序细粒度化一些,想要卖汽车,首先要有生产汽车的工厂,它可以生产各种型号的汽车。好,一个简单的工厂模式,就先到此为止,它的思路是:
1、我要有各种型号汽车的类,他们继承自同一种汽车的基础类,都拥有发动和跑的功能。
2、我要有生产各种型号汽车的工厂简单单体,给它一个型号,它就能生产出来相应型号的汽车,并且有发动和跑的功能。
3、最后,我要有卖汽车的商店,它可以卖各种型号的汽车,具有发动和跑的功能。
好的,思路确定了,我们现在需要一些工具,一个是继承的工具,一个是接口工具(检查汽车是否有发动和跑的功能)。我们分别采用寄生继承和鸭式辩型接口作为基础js文件:

// 我自己的命名空间
var TRHL = {};
// 鸭式辩型接口
TRHL.Interface = function (name, methods) {
  if (arguments.length !== 2) {
	throw new Error('this instance Interface constructor length must be 2 !');
  }
  this.name = name;
  this.methods = [];
  for (var i = 1; i < arguments.length; i++) {
	if (typeof arguments[i] !== 'string') {
	  throw new Error('the interface method type is wrong !');
	}
  }
};

TRHL.Interface.ensureImplements = function (object) {
  if (arguments.length < 2) {
	throw new Error('Interface.ensureImplements method constructor arguments must be >= 2!');
  }
  for (var i = 1; i < arguments.length; i++) {
    var instanceInterface = arguments[i];
    if (instanceInterface.constructor !== TRHL.Interface) {
	  throw new Error('the arguments constructor not be Interface Class');
	}
	for (var j = 0; j < instanceInterface.method.length; j++) {
      var methodName = instanceInterface.method[j];
      if (!object[methodName] || object[methodName] !== 'function') {
		throw new Error("the method\'s name '" + methodName + "' is not found!'");
	  }
	}
  }
}

// 只继承父类的原型对象
TRHL.extend = function (sub, sup) {
  var F = new Function();
  F.prototype = new sup();
  sub.prototype = F.prototype;
  sub.prototype.constructor = sub;
  sub.superClass = sup.prototype;
  if (sup.prototype.constructor === Object.prototype.constructor) {
    sup.constructor = sup;
  }
}

好了,工具有了,下面我们开始构建工厂

// 1 各种型号的汽车类,继承自同一个汽车基础类,拥有发动和跑的方法
var carInterface = new TRHL.Interface('carInterface', ['rstart', 'run']); // 建立接口对象
// 基础汽车类
function BaseCar () {};
BaseCar.prototype = {
  constructor: BaseCar,
  start: function () { alert(this.constructor.name + 'start...') };
  run: function () { alert(this.constructor.name + 'run...') };
}
// 各种型号的汽车类
function Benz () {};
TRHL.extend(Benz, BaseCar);
Benz.prototype.drive = function () { alert('Benz drive') }; // 可以添加自己的方法
function BMW () {};
TRHL.extend(BMW, BaseCar);
function Audi () {};
TRHL.extend(Audi, BaseCar);
// 2 生产汽车的工厂简单单体,给它一个型号,它就能生产出来相应型号的汽车,并且有发动和跑的功能。
carFactory = {
  createCar: function (type) {
  	var car = null;
	switch (type) {
	  case 'Benz': car = new Benz();
	  case 'BMW': car = new BMW();
	  case 'Audi': car = new Audi();
	  default: alert('no this type car !');
	}
	TRHL.Interface.ensureImplements(car, carInterface); // 用于检查汽车实例是否具有发动和跑的方法
	return car;
  }
}
// 3 卖汽车的商店类,可以出售各种型号的汽车,并且具有发动和跑的功能
function CarShop () {};
CarShop.prototype = {
  constructor: CarShop,
  sellCar: function (type) {
	var car = carFactory.createCar(type);
	return car;
  }
}
// 使用方法:
var shop1 = new CarShop();
c1 = shop1.sellCar('Benz');
c1.start();
c1.run();
c1.drive();

好的简单工厂就这样构建好了,这就是面向对象解决问题的方法,思路是最重要的。

2、复杂工厂

那么,在简单工厂的基础上,我们可以继续的细化,首先,卖汽车的商店不可能什么样的汽车都卖,所以要有不同的商店,出售不同品牌的汽车,其次,生产汽车的工厂代码结构可以继续优化一下,对于第一个问题,可以引入抽象类的概念,将商店转化为抽象类,第二个问题,可以用eval来解决:

// 1 将商店转化为抽象类
function CarShop () {};
CarShop.prototype = {
  constructor: CarShop,
  sellCar: function (type) {
	this.abstractSellCar(type);
  },
  abstractSellCar: function () {
	throw new Error('this method is abstract...');
  }
}
// 2 建立出售各种品牌汽车的商店类
function BenzCarShop () {};
TRHL.extend(BenzCarShop, CarShop);
BenzCarShop.prototype = {
  constructor: BenzCarShop,
  sellCar: function (type) { // 覆写sellCar方法
	var car = null;
	var types = ['Benz']; //有各种型号的Benz汽车
	for (t in types) {
	  if (types[t] === type) {
		car = carFactory.createCar(type);
		return car;
	  } else {
		throw new Error('no this type car !');
	  }
	}
  }
}
function BMWCarShop () {};
TRHL.extend(BMWCarShop, CarShop);
BMWCarShop.prototype = {
  constructor: BMWCarShop,
  sellCar: function (type) { // 覆写sellCar方法
	var car = null;
	var types = ['BMW']; //有各种型号的BMW汽车
	for (t in types) {
	  if (types[t] === type) {
		car = carFactory.createCar(type);
		return car;
	  } else {
		throw new Error('no this type car !');
	  }
	}
  }
}
function AudiCarShop () {};
TRHL.extend(AudiCarShop, CarShop);
AudiCarShop.prototype = {
  constructor: AudiCarShop,
  sellCar: function (type) { // 覆写sellCar方法
	var car = null;
	var types = ['Audi']; //有各种型号的Audi汽车
	for (t in types) {
	  if (types[t] === type) {
		car = carFactory.createCar(type);
		return car;
	  } else {
		throw new Error('no this type car !');
	  }
	}
  }
}
// 优化生产汽车的工厂
var carFactory = {
  createCar: function (type) {
	var car = null;
	car = eval('new '+type+'()');
	TRHL.Interface.ensureImplements(car, CarInterface)
	return car;
  }
}
// 各种型号的汽车类不变
var CarInterface = new TRHL.Interface('CarInterface', ['start', 'run']);

function BaseCar () {};
BaseCar.prototype = {
  constructor: BaseCar,
  start: function () { alert(this.constructor.name + '...start') },
  run: function () { alert(this.constructor.name + '...run') },
}

function Benz () {};
TRHL.extend(Benz, BaseCar);
Benz.prototype.BenzDrive = function () { alert('Benz drive') };
function BMW () {};
TRHL.extend(BMW, BaseCar);
function Audi () {};
TRHL.extend(Audi, BaseCar);
// 使用方法:
var shop1 = new BenzCarShop();
c1 = shop1.sellCar('Benz');
c1.run();
c1.drive();
var shop2 = new AudiCarShop();
c2 = shop2.sellCar('Audi');
c2.run();

四、桥模式

桥模式的目的主要是为了把两个业务逻辑单元解耦,方便单元测试,主要有三种应用场景:1、事件监听回调,2、特权函数,3、组合单体

1、事件监听回调

// 注册点击事件业务逻辑
var inp = document.getElementById('inp');
inp.addEventListener('click', bridgeHandler, false);
// 桥模式解耦
function bridgeHandler (this.value) {
  var msg = this.value;
  sendRequest(msg);
}
// 发送数据业务逻辑
function sendRequest (msg) {
  alert('成功发送数据'+msg);
}

2、特权函数

function PublicClass () {
  // 内部私有函数
  function privateMethod () {
	alert('执行了很复杂的操作')
  }
  // 通过桥特权函数调用私有函数
  this.bridgeMethod = function () {
	privateMethod();
  }
}
var p1 = new PublicClass();
p1.bridgeMethod(); // 调用桥特权函数

3、组合单体

function Class1 (a, b, c) {
  this.a = a;
  this.b = b;
  this.c = c;
}
function Class2 (d, e) {
  this.d = d;
  this.e = e;
}
function BridgeClass (a, b, c, d, e) {
  this.class1 = new Class1(a, b, c);
  this.class2 = new Class2(d, e);
}

桥模式的代码并不复杂,重要的是理解思想:解耦业务逻辑,方便单元测试。

五、组合模式

组合模式通常用来处理树状结构,组合模式的应用场景和特点:
场景:
1 存在一批组织成某种层次体系的对象
2 希望对这批对象或其中的一部分对象执行一个操作
特点:
1 组合模式中只有两种类型的对象:组合对象、叶子对象
2 这两种类型都实现同一批接口
3 一般我们会在组合对象中调用其方法并隐式调用“下级对象”的方法(一般采用递归的形式去做)
举个例子:假如我们现在有如下一个树状结构:

公司 ---->财务部门----->张一
                 			 ----->张二
   					         ----->张三
      ----->销售部门----->张四
                            ----->张五
                            ----->张六

具体的方法只能由树状结构的末端每一个人去执行,那么我们如何去构建这个树状结构?传统的方法可能是这样的:

// 总公司类
function Org (name) {
  this.name = name;
  this.depts = [] //保存下级部门
}
Org.prototype = {
  constructor: Org,
  addDepts: function (child) { this.depts.push(child); return this; };
  getDepts: function () { return this.depts; }
}
// 部门类
function Dept (name) {
  this.name = name;
  this.persons = [] //保存下级员工
}
Dept.prototype = {
  constructor: Dept,
  addPersons: function (child) { this.persons.push(child); return this; };
  getPersons: function () { return this.persons; }
}
// 员工类
function Person (name) {
  this.name = name;
}
Person.prototype = {
  constructor: Person;
  hardWorking: function () { document.write(this.name + 'hard working...') };
  sleeping: function () { document.write(this.name + 'sleeping...') };
}
// 生产对象
var p1 = new Person('张1');
var p2 = new Person('张2');
var p3 = new Person('张3');
var p4 = new Person('张4');
var p5 = new Person('张5');
var p6 = new Person('张6');

var dept1 = new Dept('财务部门');
dept1.addPersons(p1).addPersons(p2).addPersons(p3);
var dept2 = new Dept('销售部门');
dept2.addPersons(p4).addPersons(p5).addPersons(p6);

var Org = new Org('总公司');
Org.addDepts(dept1).addDepts(dept2);
// 需求:具体的让张3工作
// 这个时候,我们要通过一层层的循环将张3找到,调用hardWorking方法
for (var i = 0, dept = Org.getDepts(); i < dept.length; i++) {
  for (var j = 0; person = dept[i].getPersons(); j < person.length; j++) {
	if (person[j] === '张3') {
	  person[j].hardWorking();
	}
  }
}

那么现在问题来了,如果公司的组织结构发生了变化怎么办?或者需求发生改变:我想让某个部门的人员努力工作怎么办?这对于传统方法,是非常麻烦的,需要重写整个结构和循环。这个时候,我们就会用到组合模式,让我们再来看一下组合模式的应用场景和特点:
场景:
1 存在一批组织成某种层次体系的对象
2 希望对这批对象或其中的一部分对象执行一个操作
特点:
1 组合模式中只有两种类型的对象:组合对象、叶子对象
2 这两种类型都实现同一批接口
3 一般我们会在组合对象中调用其方法并隐式调用“下级对象”的方法(一般采用递归的形式去做)
我们希望只通过Org.hardWorking(‘XXX’),就可以让我想要的对象执行hardWorking,好,我们让树状结构再复杂一点,然后按照组合模式的特点对树状结构进行代码重构:

  公司 -----北京分公司---->北京财务部门----->张一
							                                   ---->张二
							                                   ----->张三
                    -             ---->北京销售部门----->张四
								                               ----->张五
								                               ----->张六
	      -----上海分公司---->上海财务部门----->张七
							                                   ----->张八
							                                   ----->张九
				                 ----->上海销售部门----->张十
							                                   ----->张十一
							                                   ----->张十二

组合对象的本质其实还是递归循环,所以我们先写一个方法,目的是可以循环多维数组,并让每个元素执行一个回调函数fn:

Array.prototype.each = function (fn) {
  try {
    this.i || (this.i = 0); // 计数器,记录当前遍历元素的位置
    if (this.length > 0 && fn.constructor == Function) {
      // 循环遍历数组
      while(this.i < this.length) {
        // 获取数组的每一项
        var el = this[this.i];
        // 如果当前元素获取到了,并且当前元素是一个数组
        if (el && el.constructor == Array) {
          // 递归操作
          el.each(fn);
        } else {
          fn.call(el, el); // 将fn绑定在el上,并且将el作为参数传过去
        }
        this.i++;
      }
      this.i = null; //释放内存
    }
  } catch (error) {
    // do something
  }
  return this;
}

好了,下面正式开始:

// 组合对象的接口和叶子对象的接口,接口是用鸭式辩型法写的
var CompositeInterface = TRHL.Interface('CompositeInterface', ['addChild', 'getChild']);
var LeafInterfece = TRHL.Interface('LeafInterfece', ['hardWorking', 'sleeping']);
// 组合对象的类
function Composite (name) {
  this.name = name;
  this.type = 'Composite'; // 表明类型是组合对象
  this.children = [];
}
// 让组合对象实现统一的接口'addChild', 'getChild','hardWorking', 'sleeping'
Composite.prototype = {
  constructor: Composite,
  addChild: function (child) {
	this.children.push(child);
	return this; // 链式调用
  },
  getChild: function (name) { // 组合对象的查找方法,用来递归查找某个组合对象下面的叶子对象,因为叶子对象是具体执行者
  	var element = []; // 装载叶子对象的数组
  	// 定义一个函数,递归将叶子对象推入element
  	var pushLeaf = funciton (item) {
	  if (item.type === 'Composite') {
		item.children.each(arguments.callee); // 如果是组合对象,递归调用pushLeaf
	  } else if (item.type === 'Leaf') {
		element.push(item); // 如果是叶子对象,推入element
	  }
	}
	if (name && this.name !== name) { // 传入name的情况,需要按name递归查找
	  this.children.each(function (item) {
	  	// 1 如果找到匹配的name,递归推入element
		if (item.name === name && item.type === 'Composite') {
		  item.children.each(pushLeaf);
		}
		// 2 如果没找到匹配的name,递归调用当前函数,继续循环
		if (item.name !== name && item.type === 'Composite') {
		  item.children.each(arguments.callee)
		}
		// 3 如果传入的name是叶子对象,直接推入element
		if (item.name === name && item.type === 'Leaf') {
		  element.push(item);
		}
	  })  
	} else {  // 没有传入name的情况,递归查找所有叶子对象
	  this.children.each(pushLeaf);
	}
	return element;
  },
  hardWorking: function () {
	var leafObject = this.getChild();
	for (var i = 0; i < leafObject.length; i++) {
	  leafObject[i].hardWorking();
	}
  },
  sleeping: function () {
	var leafObject = this.getChild();
	for (var i = 0; i < leafObject.length; i++) {
	  leafObject[i].sleeping();
	}
  }
}
// 叶子对象的类
function Leaf (name) {
  this.name = name;
  this.type = 'Leaf'; // 表明类型是叶子对象
}
// 让叶子对象实现统一的接口'addChild', 'getChild','hardWorking', 'sleeping'
Leaf.prototype = {
  constructor: Leaf,
  addChild: function () {
	throw new Error('this method is disabled...') // 叶子节点无法添加子节点
  },
  getChild: function (name) {
	if (this.name === name) {
	  return this;
	}
	return null
  },
  hardWorking: function () {
	document.write(this.name + 'hardWorking');
  },
  sleeping: function () {
	document.write(this.name + 'sleeping');
  }
}
// 建立对象,传入数据
var p1 = new Leaf('张1');
var p2 = new Leaf('张2');
var p3 = new Leaf('张3');
var p4 = new Leaf('张4');
var p5 = new Leaf('张5');
var p6 = new Leaf('张6');
var p7 = new Leaf('张7');
var p8 = new Leaf('张8');
var p9 = new Leaf('张9');
var p10 = new Leaf('张10');
var p11 = new Leaf('张11');
var p12 = new Leaf('张12');

var dept1 = new Composite('北京财务部门');
dept1.addChild(p1).addChild(p2).addChild(p3);
var dept2 = new Composite('北京销售部门');
dept2.addChild(p4).addChild(p5).addChild(p6);
var dept3 = new Composite('上海财务部门');
dept3.addChild(p7).addChild(p8).addChild(p9);
var dept4 = new Composite('上海销售部门');
dept4.addChild(p10).addChild(p11).addChild(p12);

var Org1 = new Composite('北京分公司');
Org1.addChild(dept1).addChild(dept2);
var Org2 = new Composite('上海分公司');
Org2.addChild(dept3).addChild(dept4);

var Org = new Composite('总公司');
Org.addChild(Org1).addChild(Org2);

Org.hardWorking();
Org.hardWorking('北京分公司');
Org.hardWorking('上海财务部门');
Org.hardWorking('张10');

好了,这就是组合模式,其实就是将树结构拆分成了组合对象和叶子对象,并且把循环递归提炼了出来,这样,不管需求如何变化,结构怎么改变,都非常方便。

六、门面模式

门面模式,是所有库的基础,也是程序员最常用的模式,就是用一个公用的方法把一些复杂的操作和函数提炼封装起来,达到复用和解耦的目的。

function a(x) { alert(x) };
function b(y) { alert(y) };
// 用门面模式封装起来
function ab (x, y) {
  a(x);
  b(y);
}

这就是最简单的门面模式,有点类似于桥模式的第三种用法,组合多个对象,当然,门面模式不止如此,它还可以更复杂一些,
现在我有一个元素,想要给它设置颜色样式:

window.onload(function () {
  var el = document.getElementById('div1');
  el.style.color = 'red';
})

现在需求变了,我有多个元素,想要给它们设置某个样式,这时候为了避免重复性的代码,就可以使用门面模式:

// 用门面模式封装
function setStyle (el, prop, val) {
  for (var i = 0; i < el.length; i++) {
	document.getElementById(el[i]).style[prop] = val;
  }
}
setStyle(['div1', 'div2', 'div3'], 'color', 'blue');

现在需求又变了,我有多个元素,并且我想给它们设置多个样式,这个时候我们就可以用门面模式进行二次封装:

function setCss (el, options) {
  for (prop in options) {
	setStyle(el, prop, options[prop]);
  }
}
setCss (['div1', 'div2', 'div3'], {
  color: 'red',
  fontSize: '20px'
})

这就是门面模式,但是要注意不要滥用门面模式,否则就会把简单的问题复杂化

七、适配器模式

适配器模式可以处理一些类与API不匹配、不能一同使用的情况,使用这种模式的对象又叫做“包装器”(wrapper)。注意适配器模式虽然和门面模式比较类似,但是门面模式只是为了简化一个接口,不提供额外的选择;适配器则要把一个接口转化为另一个接口,它并不会滤除某些能力,也不会简化接口。
举个例子:
比如现在要开发一个项目,有些程序员会Prototype的框架,有些程序员会YUI的框架,这时候我们应该选用什么框架?
这个时候就用到适配器了,我们可以选用YUI的框架,但是Prototype的程序员,按照Prototype框架的规则,该怎么写还怎么写。
就拿这两个框架的dom选择器做一个例子,这个是Prototype获取dom元素的方法:

$('div1', 'div2')

可以传一个id,也可以传多个id,它的底层实现大概是这样:

// Prototype 不需要传递任何参数, 一个id写一个参数,多个id写多个参数
function $ () {
  var elements = [];
  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element === 'string') {
      element = document.getElementById(element);
    }
    if (arguments.length === 1) {
      return element;
    }
    elements.push(element);
  }
  return elements;
}

这是YUI获取dom元素的方法:

YAHOO.get('div1');
YAHOO.get(['div1', 'div2'])

只能传递一个参数,要么是一个字符串,要么是一个数组,它的底层实现大概是这样的:

// YUI 传递了一个参数, 类型是数组或者字符串
var YAHOO = {};
YAHOO.get = function (el) {
  if (typeof el === 'string') {
    return document.getElementById(el);
  }
  if (el instanceof Array) {
    var elements = [];
    for (var i = 0; i < el.length; i++) {
      elements[elements.length] = YAHOO.get(el[i]);
    }
    return elements;
  }
  if (el) {
    return el;
  }
  return null;
}

下面,开始做一个适配器,目的是让Prototype程序员可以使用YAHHO的框架,但是用Prototype的规则写代码:

YAHOO.get = YUIToPrototypeAdapter;
function YUIToPrototypeAdapter () {
  if (arguments.length === 1) { // 这是YUI常用的写法,只传递一个参数
	var e = arguments[0];
	return $.aplly(window, e instanceof Array ? e : [e]);
  } else { // 这是Prototype程序员的写法,传递了多个id
	return $.apply(window, arguments);
  }
}

好了,使用了这个适配器之后,prototype的程序员就可以这样写代码了:

YAHOO.get('div1', 'div2');

雅虎的程序员也可以像原来一样写:

YAHOO.get(['div1', 'div2']);

八、装饰者模式

装饰者模式是一种为对象添加新特性的技术,它可以透明的把对象包装在具有相同接口的另一个对象中。这样我们就可以为对象添加一些方法或者行为,然后将方法调用传递给原始对象,我们先来看看再函数中它是如何使用的,比如我现在有下面这样一个函数:

function getDate () {
  return (new Date()).toString();
}

现在,我想要让这个函数的返回的结果全部大写,这个时候做一个装饰器就好:

function upperCaseDecorator (fn) {
  return function () {
	return fn.apply(this, arguments).toUpperCase();
  }
}
// 然后这样调用就可以
var getDecoratorDate = upperCaseDecorator(getDate);
alert(getDecoratorDate());

可以看到,我们在不改变原有函数的基础上,为函数添加了新的特性,下面看一下装饰者模式在面向对象中的使用,假如我现在有一个汽车的类如下:

function Car (car) {
  // 这个属性,就是为了让子类继承的,可以让子类多一个父类的引用
  // 这样子类在继承了这个属性之后,将来构造子类的实例时可以传入父类的实例,存在这里,方便子类的实例进行引用,装饰,修改
  this.car = car;
}
Car.prototype = {
  constructor: Car,
  getPrice: function () { return 100000 },
  assemble: function () { document.write('组装汽车') }
}
var car = new Car();

现在,我想在不改变原有类的基础上,改写一下里面的方法,同时,在我新的方法中,还要引用原来的类中的方法,比如,我创建一个给汽车安装车灯的类,安装车灯之后,汽车的价格会在原有100000元的基础上加1000,这个时候就需要装饰者模式:

function LightDecorator (car) {
  // 这句话继承了Car类的构造函数,为自己添加了一个this.car = car;
  LightDecorator.superClass.constructor.call(this, car);
}
// 这句话继承了Car类的原型
TRHL.extend(LightDecorator, Car);
LightDecorator.prototype = {
  constructor: LightDecorator,
  getPrice: function () { return this.car.getPrice() + 10000 }, // this.car引用了原始Car类里面的getPrice方法
  assemble: function () { document.write('组装车灯...') }
}
// 使用方法
var car = new Car();
car = new LightDecorator(car); // 注意这里传入了原始类的实例,存在了this.car中
alert(car.getPrice()); // 110000

补充一下extend方法:

// 实现只继承父类的原型对象
TRHL.extend = function (sub, sup) {
  var F = new Function();
  F.prototype = sup.prototype;
  sub.prototype = new F();
  sub.prototype.constructor = sub;
  sub.superClass = sup.prototype;
  if (sup.prototype.constructor === Object.prototype.constructor) {
    sup.constructor = sup;
  }
}

这就是装饰者模式,顾名思义,其是对原始对象的装饰,所以必然是要引用原始对象的。

九、享元模式

享元模式是一种优化模式,它最适合解决因创建大量类似对象而累及性能的问题。这种模式在javascript中尤其有用,因为复杂的javascript代码可能很快会用光浏览器的所有内存。通过把大量独立对象转化为少量共享对象,可以降低运行web应用程序所需的资源数量。下面通过2个例子来说明:

1、划分内在数据和外在数据

当一个对象内部有很多属性,我们在创建大量这种对象时,可以将对象的属性划分为内在数据(static)和外在数据,内在数据是可能会大量重复的数据,外在数据是很可能各不相同,比如我现在有一个传统的汽车的类:

function Car (make, model, year, owner, tag, renewDate) { // 属性分别是: 出场商 型号 生产日期 拥有者 车牌号 最近登记时间
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
  this.tag = tag;
  this.renewDate = renewDate;
}
Car.prototype = {
  constructor: Car,
  getMake: function () { return this.make; },
  getModel: function () { return this.model; },
  getYear: function () { return this.year; },
  renewRegistration: function (newRnewDate) {
    this.renewDate = newRnewDate;
  }
}

好,现在我要创建500万个汽车实例,让我们测试一下在谷歌浏览器,它会耗费多少资源:

var stime = new Date();
var arr = [];
for (var i = 0; i < 5000000; i++) {
  arr.push(new Car('上海大众', '迈腾', '2019-10-11', 'TRHL', 'bj0001', '2019-10-11'));
}
var etime = new Date();
console.log(etime - stime); // 占用内存652M  运行时间1613ms

现在,让我们用享元模式来优化一下,首先分析一下Car类的属性,我们发现(出场商,型号,生产日期)可能很多都是一样的,我们将其划分为内在数据;(拥有者,车牌号,登记时间)这个很可能各不相同,我们将其划分为外在数据。下面我们的思路是:能不能将内在数据的重复项去掉,然后和外在数据组合起来,这样就可以节省创建相同内在数据所耗费的资源,好了,思路确定了,怎么实施?首先创建2个闭包单体,一个用来生产内在数据,一个用来整合,当然原始的Car类也需要改造一下:

function Car (make, model, year) { // 将外在数据和方法都去掉
  this.make = make;
  this.model = model;
  this.year = year;
}
Car.prototype = {
  constructor: Car,
  getMake: function () { return this.make; },
  getModel: function () { return this.model; },
  getYear: function () { return this.year; }
}
// 第一个闭包单体,作为生产内在数据的工厂
var CarFactory = (function () {
  var createdCars = {}; // 用于承载创建的内在数据实例
  return {
	createCar: function (make, model, year) {
	  // 将make+model+year作为唯一的标识key,如果有重复的,返回当前对象,不创建新对象
	  if (createdCars[make+model+year]) {
		return createdCars[make+model+year];
	  } else {
	    var car = new Car(make, model, year);
	    createdCars[make+model+year] = car;
		return car;
	  }
	}
  }
})()
// 第二个闭包单体,用于整合外在数据和内在数据
var carRecordManager = (function () {
  var carRecordDataBase = {}; // 用于承载整合好的实例
  return {
	addCarRecord: function (make, model, year, owner, tag, renewDate) {
	  var car = CarFactory.createCar(make, model, year);
	  carRecordDataBase[tag] = { // 将tag作为唯一标识key
		car: car,
		owner: owner,
		renewDate: renewDate
	  }
	  return carRecordDataBase[tag];
	}
	// 当然还要添加外在数据的方法
	renewRegistration: function (tag, newRenewDate) {
	  carRecordDataBase[tag].renewDate = newRenewDate;
	}
  }
})()

好了,我们用享元模式改造完了,下面测试一下:

var stime = new Date();
var arr = [];
for (var i = 0; i < 5000000; i++) {
  arr.push(CarRecordManager.addCarRecord('上海大众', '迈腾', '2019-10-11', 'TRHL', 'bj0001', '2019-10-11'));
}
var etime = new Date();
console.log(etime - stime); // 占用内存551M  耗费时间397ms

怎么样,是不是优化了很多?享元模式,顾名思义,就是找到重复项,去除重复项,共享同一个方法或者对象。

2、日历优化

日历组件在开发中经常用到,比如我现在要写一个日历组件,我的思路是什么?首先,要有3个类,年类,月类,日类。类之间还有包含的关系,年包含月,月包含日,那么,我希望我new一个年的类,它可以创建12个月的类,每个月的类还要创建30个左右日的类,当然,为了展示日历,每个类还需要一个display的方法,好了,思路确定了,开始撸代码:

// 年类
var CalendarYear = function (year, parent) { // 传入年份和挂载的父节点
  this.year = year;
  this.element = document.createElement('div');
  this.element.style.display = 'none';
  parent.appendChild(this.element);
  this.months = []; // 包含月
  // 判断闰年
  var isLeapYear = function (y) {
	return (y > 0) && !(y % 4) && ((y % 100) || !(y % 400));
  }
  this.numDays = [31, isLeapYear(this.year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; // 每个月有多少天
  for (var i = 0 ; i < 12; i++) {
    this.months[i] = new CalendarMonth(i, this.numDays[i], this.element); // new出12个月的实例
  }
}
// 添加年的display方法
CalendarYear.prototype = {
  constructor: CalendarYear,
  display: function () {
    for (var i = 0; i < this.months.length; i++) {
	  this.months[i].display();
	}
	this.element.style.display = 'block';
  }
}
// 月类
var CalendarMonth = function (monthNum, numDays, parent) {
  this.monthNum = monthNum;
  this.element = document.createElement('div');
  this.element.style.display = 'none';
  parent.appendChild(this.element);
  this.days = [];
  for (var i = 0; i < numdays; i++) {
	this.days[i] = new CalendarDay(i + 1, this.element);  // new相应天数的CalendarDay
  }
}
CalendarMonth.prototype = {
  constructor: CanlendarMonth,
  display: function () {
	for (var i = 0; i < numDays; i++) {
	  this.days[i].display();
	}
	this.element.style.display = 'block';
  }
}
// 日类
var CalendarDay = function (date, parent) {
  this.date = date;
  this.element = document.createElement('div');
  this.element.style.display = 'none';
  parent.appendChild(this.element);
}
CalendarDay.prototype = {
  constructor: CalendarDay,
  display: function () {
    this.element.style.display = 'inline-block';
	this.element.innerHTML = this.date;
  }
}

// 创建日历
window.onload = function () {
  var app = document.getElementById('app');
  var myCalendar = new CalendarYear('2019', app);
  myCalendar.display();
}

好了,日历写完了,我们可以看到,每new一个CalendarYear,就会new出来12个CalendarMonth和365个CalenderDay,可以发现,性能的瓶颈是那365个CalendarDay,可不可以用享元模式优化一下呢,再看一下日类:

// 日类
var CalendarDay = function (date, parent) {
  this.date = date;
  this.element = document.createElement('div');
  this.element.style.display = 'none';
  parent.appendChild(this.element);
}
CalendarDay.prototype = {
  constructor: CalendarDay,
  display: function () {
    this.element.style.display = 'inline-block';
	this.element.innerHTML = this.date;
  }
}

可以看出来,365个日类基本没有什么太大的不同,对它进行如下改造:

var CalendarDay = function () {};
CalendarDay.prototype = {
  constructor: CalendarDay,
  display: function (date, parent) {
    var element = document.createElement('div');
    parent.appendChild(element);
    element.style.display = 'inline-block';
	element.innerHTML = date;
  }
}
var calendarDaySingleInstance = new CalendarDay;

现在月类没必要new出来365个对象了,我们只需要calendarDaySingleInstance这一个对象,再给月类的display传参就可以了,修改月类代码如下:

// 月类
var CalendarMonth = function (monthNum, numDays, parent) {
  this.monthNum = monthNum;
  this.element = document.createElement('div');
  this.element.style.display = 'none';
  parent.appendChild(this.element);
  this.days = [];
  for (var i = 0; i < numdays; i++) {
	this.days[i] = calendarDaySingleInstance;  // 只需要new 一个对象就可以了
  }
}
CalendarMonth.prototype = {
  constructor: CanlendarMonth,
  display: function () {
	for (var i = 0; i < numDays; i++) {
	  this.days[i].display(i + 1, this.element); // 传入参数
	}
	this.element.style.display = 'block';
  }
}

所有的日类共享一个元类:calendarDaySingleInstance,不用拓展新的内存了,日历的功能没有任何影响。
这就是享元模式,关键在于分析重复性的类,数据,从而达到改造,优化的目的。

十、代理模式

代理是一个对象,它可以用来控制对一个对象的访问。它与这个对象实现了同样的接口,并且会把任何方法调用传递到这个接口,代理对象不会添加方法或者修改方法(如装饰者),也不会简化接口(如门面模式)。它实行的接口与本体完全相同,所有对它进行的方法调用都会传递给本体。举个例子,我现在有一个图书馆的类:

// 书籍的类
var Book = function (id, title, author) {
  this.id = id;
  this.title = title;
  this.author = author;
}
// 图书馆的类
var Library = function (books) {
  this.books = books;
}
Library.prototype = {
  constructor: Library,
  addBook: function (book) {
    this.books[id] = book;
  },
  findBook: function (id) {
    if (this.books[id]) {
      return this.books[id];
    }
    return null;
  },
  borrowBook: function (id) {
    // 电脑登记,交押金
    return this.findBook(id);
  },
  returnBook: function (book) {
    // 电脑登记...已还
    // 计算费用
    this.books[book.id] = book;
  }
}

现在,我不想每一次都实例化图书馆这个类,我想要对它的实例化进行节制,这个时候就需要创建一个代理对象:

var LibraryProxy = function (books) {
  alert('产生了代理对象,并没有真正产生本体对象');
  this.books = books;
  this.library = null;
}
LibraryProxy.prototype = {
  constructor: LibraryProxy,
  initializeLibrary: function () {
    if (this.library === null) {
      this.library = new Library(this.books);
    }
  },
  addBook: function (book) {
    this.initializeLibrary();
    this.library.addBook(book);
  },
  findBook: function (id) {
    this.initializeLibrary();
    alert('产生了本体对象');
    return this.library.findBook(id);
  },
  borrowBook: function (id) {
    this.initializeLibrary();
    this.library.borrowBook(id);
  },
  returnBook: function (book) {
    this.initializeLibrary();
    this.library.borrowBook(book);
  }
}

好了,代理对象写完了,这样使用:

var proxy = new LibraryProxy({
  '01': new Book('01', 'Javascript', 'z3'),
  '02': new Book('02', 'Java', 'z4')
})
// 在调用下面这个方法之前,并没有真正实例化图书馆对象
alert(proxy.findBook('02').author);

十一、观察者模式

观察者模式可以对程序中某个对象的状态进行观察,并在其发生改变时能够得到通知,观察者模式中存在两种角色:观察者和被观察者,也可以成为发布者和订阅者,用例子来说明:

// 被观察者的类(发布者)
var Publish = function (name) {
  this.name = name;
  // 1 被观察者(发布者)的类中要有观察者(订阅者)的集合
  // 2 集合里的元素必须是函数类型,因为观察者模式的本质就是状态改变时(发布消息时)触发执行函数,从而让观察者得到通知
  this.subscribers = []; // fn1, fn2, .....
}
// 被观察者的状态改变,或者说是发布者发布消息
Publish.prototype.deliver = function (news) {
  var publish = this; // 保存一下当前被观察者(发布者)的信息
  // 观察者模式的核心,当调用deliver方法时,就会触发自己所有的观察者执行fn函数
  this.subscribers.forEach(function (fn) {
	fn(news, publish);
  })
}

// 观察者开始观察被观察者 或者说是 订阅者开始订阅发布者
Function.prototype.subscribe = function (publish) {
  var sub = this; // 保存一下观察者(订阅者)的信息
  // 1 判断此观察者是否已经存在
  var alreadyExists = publish.subscribers.some(function (item) {
	return item === this;
  })
  // 2 如果不存在,将其加入到被观察者的subscribers中去
  if (!alreadyExists) { publish.subscribers.push(this) }
  return this; //链式调用
}

// 取消观察(订阅)的方法
Function.prototype.unsubscribe = function (publish) {
  var sub = this;
  // 将观察者从subscribers中删除
  publish.subscribers = publish.subscribers.filter(function (item) {
	return item !== this
  })
  return this;
}

这就是观察者模式的基本架构,具体这样使用:

// 实例化被观察者(发布者)
var pub1 = new Publish('被观察者1');
var pub2 = new Publish('被观察者2');
var pub3 = new Publish('被观察者3');
// 实例化观察者(订阅者)其实每个函数都可以是观察者(订阅者)
var sub1 = function (news) { alert(news, arguments[1]) };
var sub2 = function (news) { alert(news, arguments[1]) };
// 开始观察(订阅消息)
sub1.subscribe(pub1).subscribe(pub2).subscribe(pub3);
sub2.subscribe(pub1).subscribe(pub2);
// 状态改变(发布消息)
pub1.deliver('我的状态改变了'); // sub1 sub2 都会收到
pub3.deliver('我的状态也改变了'); // sub2收不到
// 还可以取消观察(订阅)
sub1.unsubscribe(pub3);

十二、命令模式

命令模式是一种封装方法调用的方式,看到了封装,我们会想到门面模式,但是命令模式与门面模式有所不同:门面模式是对一些重复性、程序化的函数进行封装,方便以后调用;而命令模式可以对方法调用进行参数化处理和传送,也就是说,命令模式更侧重于对某个对象的部分行为进行精确的控制。看到这里,我们是否会想到游戏?没错,游戏中经常使用命令模式。
命令模式的整体架构分为三个部分:1、客户,2、调用者,3、接收者。
客户创建命令,调用者执行命令…只是执行命令,具体的脏活累活谁来干?接收者,他在执行命令时进行相应的操作。
下面用一个例子说明一下命令模式的简单架构:

// 我是接收者,脏活累活我来干
var worker = {
  work1: function (a, b) { alert(a, b, '复杂的操作') },
  work2: function (c, d) { alert(c, d, '还是复杂的操作') },
  work2: function (e, f) { alert(e, f'依然复杂的操作') },
}
// 我是调用者,我记住了很多命令,我只执行各种命令
var Command1 = function (worker) {};
Command1.prototype.execute = function () {
  worker.work1(a+10, b-10); // 可以传参
  worker.work3(e-10, f+10); // 可以传参
}
var Command2 = function (worker) {};
Command1.prototype.execute = function () {
  worker.work2(c+10, d+10); // 可以传参
}
// 我是客户我最牛,我决定用啥命令
var c1 = new Command1(worker);
c1.execute();

理解了命令模式的基本架构之后,再用一个实际的例子加深一下理解,比如我现在做了一个功能,可以通过改变参数,让一个小方块上下左右移动,并且我把它封装成一个类,可以重复生成实例:

var Cursor = function (width, height, parent) { // 画布的宽高,挂载的结点
  this.width = width;
  this.height = height;
  this.position = { // 用于保存小方块的位置
	x: width / 2,
	y: height / 2
  }
  // 创建画布
  this.canvas = document.createElement('canvas');
  this.canvas.width = this.width;
  this.canvas.height = this.height;
  parent.appendChild(this.canvas);
  // canvas上下文元素
  this.ctx = this.canvas.getContext('2d');
  this.ctx.fillStyle = 'red';
  this.move(0, 0);
}
Cursor.prototype = {
  constructor: Cursor,
  move: function (x, y) {
	this.position.x += x;
	this.position.y += y;
	this.canvas.clearRect(0, 0, this.width, this,height);
	this.canvas.fillRect(this.position.x, this.position.y, 20, 20);
  }
}

现在,我想把小方块的移动细分为上下左右,四个命令,我每调用一次命令,它就会向一个方向移动,这个时候就用到了命令模式:

// 向上移动的命令
var MoveUp = function (cursor) {
  this.cursor = cursor; // 保存一下接收者
}
MoveUp.prototype = {
  constructor: MoveUp,
  execute: function () {
	cursor.move(0, -10);
  }
}
// 向下移动的命令
var MoveDown = function (cursor) {
  this.cursor = cursor; // 保存一下接收者
}
MoveDown.prototype = {
  constructor: MoveDown,
  execute: function () {
	cursor.move(0, 10);
  }
}
// 向左移动的命令
var MoveLeft = function (cursor) {
  this.cursor = cursor; // 保存一下接收者
}
MoveLeft.prototype = {
  constructor: MoveLeft,
  execute: function () {
	cursor.move(-10, 0);
  }
}
// 向右移动的命令
var MoveRight = function (cursor) {
  this.cursor = cursor; // 保存一下接收者
}
MoveRight.prototype = {
  constructor: MoveRight,
  execute: function () {
	cursor.move(10, 0);
  }
}

上面就是调用者,下面写一下客户:

var body = document.getElementByTagName('body')[0];
var cursor = new Cursor(400, 400, body);
var up = new MoveUp(cursor);
var down = new MoveDown(cursor);
var left = new MoveLeft(cursor);
var right = new MoveRight(cursor);
// 执行up命令
up.execute();

可以发现,我们通过命令模式,就可以对一个对象进行精确的操控。

十三、责任链模式

责任链模式可以用来消除请求的发送者和接收者之间的耦合。链中的每个对象可以处理请求,也可以将其传给下一个对象。javascript内部就是使用了这个模式来处理时间捕获和冒泡的问题。
责任链模式中的角色:请求发出者, 接收者。
责任链模式的流程:
1、发送者知道链中的第一个接收者,它向这个接收者发出请求。
2、每一个接收者对请求进行分析,要么处理它,要么往下传递。
3、每一个接收者知道的其他对象只有一个,即它的下家对象。
4、如果没有任何接收者处理请求,那么请求将从链上离开,不同的实现对此有不同的反应。
还是拿实际问题来举例子,比如我现在手头有一批任务,然后我有一批员工,他们有各自擅长的技能,我想知道谁能完成我的任务,然后把任务交给那个人完成。面对这种问题,我们第一个想到的应该是循环,遍历每一个员工,然后找到合适的人。让我们用代码来实现一下:

// 任务类
var Assign = function (task) {
  this.task = task; // 保存任务
}
// 发出请求的类
var WorkFlow = function (assign) {
  this.assign = assign; // 保存接收的任务
}
WorkFlow.prototype = {
  constructor: WorkFlow,
  filterHandler: function (ex) { // 任务接收者的集合
	for (var i = 0; i < ex.length; i++) {
	  if (ex[i].cando === this.assign.task) { // 如果某个接收者可以处理轻轻,则处理
		ex[i].todo();
	  }
	}
	return // 否则返回
  }
}
// 任务接受者类
var Executor = function (name, cando) {
  this.name = name;
  this.cando = cando; // 可处理的任务
}
Executor.prototype = {
  constructor: Executor,
  todo: function () {
	document.write(this.name + '处理' + this.cando);
  }
}
// 实例化
var ex1 = new Executor('小1', 'javascript');
var ex2 = new Executor('小2', 'sql');
var ex3 = new Executor('小3', 'css');
var ex4 = new Executor('小4', 'java');

var assign = new Assign('java');
var wf = new WorkFLow(assign);
wf.filterHandler([ex1, ex2, ex3, ex4]); // 小4处理java

我们可以发现一个问题,上面这种模式,任务的发出者需要拥有全部处理者的数组,发出者和处理者之间的耦合太强了,下面我们用责任链的模式对它进行重构:

// 任务类
var Assign = function (task) {
  this.task = task; // 保存任务
}
// 发出请求的类
var WorkFlow = function (assign) {
  this.assign = assign; // 保存接收的任务
}
WorkFlow.prototype = {
  constructor: WorkFlow,
  filterHandler: function (executor) { // 任务接收者的集合
	if (this.assign.task === executor.cando) {
	  executor.todo();
	} else {
	  arguments.callee.call(this, executor.successor); // 递归调用下一个接收者
	}
	return // 都不符合则返回
  }
}
// 任务接受者类
var Executor = function (name, cando) {
  this.name = name;
  this.cando = cando; // 可处理的任务
  this.successor = null // 用来保存下级对象的引用
}
Executor.prototype = {
  constructor: Executor,
  todo: function () {
	document.write(this.name + '处理' + this.cando);
  },
  // 添加下级处理者的函数
  setSuccessor: function (successor) {
	this.successor = successor;
  }
}
// 实例化
var ex1 = new Executor('小1', 'javascript');
var ex2 = new Executor('小2', 'sql');
var ex3 = new Executor('小3', 'css');
var ex4 = new Executor('小4', 'java');
ex1.setSuccessor(ex2);
ex2.setSuccessor(ex3);
ex3.setSuccessor(ex4);

var assign = new Assign('java');
var wf = new WorkFLow(assign);
// 只需要传递第一个接收者
wf.filterHandler(ex1); // 小4处理java

十四、策略模式

策略模式是指:一个方法,在面对不同情况时,会采用不同的解决策略。比如我现在有一个人的类,类里面有一个跑的方法:

var Person = function (fromWhere, toWhere) {
  this.fromWhere = fromWhere;
  this.toWhere = toWhere;
}
Person.prototype.run = function () {

}

现在有一个问题,我这个跑的方法不想写死,我在不同的情况下,想要采用不同的策略。这个时候就用到了策略模式,我们可以建立一个单例:

var strategy = {
  slow: function (distance) { alert('慢速策略,耗时' + distance * 2 + '小时') };
  nomal: function (distance) { alert('普通策略,耗时' + distance + '小时') };
  fast: function (distance) { alert('快速策略,耗时' + distance / 2 + '小时') };
}

然后这样写run方法:

Person.prototype.run = function (chooseStrategy) { // 参数是一个函数
  chooseStrategy(this.toWhere - this.fromWhere);
}

此时,我们就可以采用不同的策略了:

var p1 = new Person(0, 20);
p1.run(strategy.slow); // 慢速策略,耗时40小时
p1.run(strategy.nomal); // 普通策略,耗时20小时
p1.run(strategy.fast); // 快速策略,耗时10小时

十五、模板模式

模板模式,说白了就是子类重写父类的某个方法,其他方法还是用父类的,就是js的多态,就拿做果汁为例吧:

function Fruit () {};
Fruit.prototype = {
  constructor: Fruit,
  make: function () {
	// 烧开水
	this.water();
	// 放水果
	this.cailiao();
	// 搅拌
	this.jiaobao();
	// 给用户喝
	this.drink();
  },
  water: function () { console.log('烧开水') };
  cailiao: function () { throw new Error('必须子类重写该方法') };
  jiaoban: function () { console.log('搅拌') };
  drink: function () { console.log('给用户喝') };
}

显然,材料不能写死,应该是各不相同,这时候就用到了模板模式:

function Apple () {};
// 先继承父类
Apple.prototype = new Fruit();
// 然后重写放水果的方法
Apple.prototype.cailiao = function () { console.log('放入苹果') };
var a = new Apple();
a.make();
function Pear () {};



// 先继承父类
Pear.prototype = new Fruit();
// 然后重写放水果的方法
Pear.prototype.cailiao = function () { console.log('放入梨子') };
var p = new Pear();
p.make();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值