1993klk / blog

记点东西
1 stars 0 forks source link

《JavaScript设计模式与开发实践》摘要 #4

Open 1993klk opened 4 years ago

1993klk commented 4 years ago

这篇内容,是针对这本书中提到的设计模式及原则进行截取,并展示文中的代码,以便复习巩固。

涉及的模式:

单例模式,策略模式,代理模式,迭代器模式,发布-订阅模式,命令模式,组合模式,模板方法模式,享元模式,职责链模式,中介者模式,装饰者模式,状态模式,适配器模式

涉及的原则:

单一责任原则,最少知识原则,开放封闭原则

模式

分辨模式的关键是意图而不是结构

设计模式分别被划分为创建型模式、结构型模式和行为型模式。 拿创建型模式来说,要创建一个对象,是一种抽象行为,而具体创建什么对象则是可以变化的

  1. 创建型模式的目的就是封装创建对象的变化
  2. 结构型模式封装的是对象之间的组合关系。
  3. 行为型模式封装的是对象的行为变化。

单例模式

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

//getSingle运用到了闭包和高阶函数。创建对象和管理单例的职责被分布在两个不同的方法中
var getSingle = function (fn) {
  var result;
  return function () {
    return result || (result = fn.apply(this, arguments));
  };
};

策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

//这里将不同的的算法提取出来,在调用时确定要执行的是哪个算法,需要使用者对各个策略都要了解,才能选择合适的策略
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 );

代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。在 JavaScript 开发中最常用的是虚拟代理和缓存代理。 需强调:代理和本体接口的一致性,因为对于客户来说,对代理和本体是不感知的,并且当需求变更,不再需要代理时,可能很方便得去除代理或直接请求本体

//未使用代理
var MyImage = (function () {
  var imgNode = document.createElement("img");
  document.body.appendChild(imgNode);
  var img = new Image();
  img.onload = function () {
    imgNode.src = img.src;
  };
  return {
    setSrc: function (src) {
      imgNode.src = "file://xx.gif";
      img.src = src;
    },
  };
})();
MyImage.setSrc("http://yy.jpg");

//使用虚拟代理实现图片预加载
var myImage = (function () {
  var imgNode = document.createElement("img");
  document.body.appendChild(imgNode);
  return function (src) {
    imgNode.src = src;
  };
})();
var proxyImage = (function () {
  var img = new Image();
  img.onload = function () {
    myImage(this.src);
  };
  return function (src) {
    myImage("file://xx.gif");
    img.src = src;
  };
})();
proxyImage("http://yy.jpg");

迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。 迭代器可以分为内部迭代器和外部迭代器,js中的foreach就是内部迭代器,es6中的generator就是外部迭代器。内部迭代器在调用的时候非常方便,但规则已限定,不够灵活。外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性。

发布-订阅模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。但两者又有细微的差别,观察者模式存在目标和观察者,当事件触发时,目标直接调用观察者的事件。发布—订阅模式中存在了发布者,订阅者,调度中心,发布者和订阅者对彼此完全无感知,统一都是由调度中心进行处理,类似于中介者模式,但在目的上又有所不同。

发布—订阅模式
var Event = (function () {
  var clientList = {},
    listen,
    trigger,
    remove;
  listen = function (key, fn) {
    if (!clientList[key]) {
      clientList[key] = [];
    }
    clientList[key].push(fn);
  };
  trigger = function () {
    var key = Array.prototype.shift.call(arguments),
      fns = clientList[key];
    if (!fns || fns.length === 0) {
      return false;
    }
    for (var i = 0, fn; (fn = fns[i++]); ) {
      fn.apply(this, arguments);
    }
  };
  remove = function (key, fn) {
    var fns = clientList[key];
    if (!fns) {
      return false;
    }
    if (!fn) {
      fns && (fns.length = 0);
    } else {
      for (var l = fns.length - 1; l >= 0; l--) {
        var _fn = fns[l];
        if (_fn === fn) {
          fns.splice(l, 1);
        }
      }
    }
  };
  return {
    listen: listen,
    trigger: trigger,
    remove: remove,
  };
})();
Event.listen("squareMeter88", function (price) {
  // 小红订阅消息
  console.log("价格= " + price); // 输出:'价格=2000000'
});
Event.trigger("squareMeter88", 2000000); // 售楼处发布消息

命令模式

命令模式中的命令(command)指的是一个执行某些特定事情的指令。命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品。 命令模式的意图是把请求封装为对象,从而分离请求的发起者和请求的接收者(执行者)之 间的耦合关系。在命令被执行之前,可以预先往命令对象中植入命令的接收者。

var setCommand = function (button, func) {
  button.onclick = function () {
    func();
  };
};
var MenuBar = {
  refresh: function () {
    console.log("刷新菜单界面");
  },
};
var RefreshMenuBarCommand = function (receiver) {
  return function () {
    receiver.refresh();
  };
};
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);

组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更

小的“孙对象”构成的。例如文件夹,就是一种组合

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 );

模板方法模式

模板方法模式:一种基于继承的设计模式。模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。像vue和react中的生命周期,其实就是模板方法的体现,它们各自设定好对应的接口,并在合适的时机调用,而我们只需要在子类中定义好具体的实现就行了

享元模式

享元模式是一种用于性能优化的模式,享元模式的核心是运用共享技术来有效支持大量细粒度的对象。 享元模式要求将对象的属性划分为内部状态与外部状态(状态在这里通常指属性)。享元模式的目标是尽量减少共享对象的数量,关于如何划分内部状态和外部状态,下面的几条经验提供了一些指引。

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

    职责链模式

    职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间 的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

    
    /未优化
    var order = function (orderType, pay, stock) {
    if (orderType === 1) {
    // 500 元定金购买模式
    if (pay === true) {
      // 已支付定金
      console.log("500 元定金预购, 得到 100 优惠券");
    } else {
      // 未支付定金,降级到普通购买模式
      if (stock > 0) {
        // 用于普通购买的手机还有库存
        console.log("普通购买, 无优惠券");
      } else {
        console.log("手机库存不足");
      }
    }
    } else if (orderType === 2) {
    // 200 元定金购买模式
    if (pay === true) {
      console.log("200 元定金预购, 得到 50 优惠券");
    } else {
      if (stock > 0) {
        console.log("普通购买, 无优惠券");
      } else {
        console.log("手机库存不足");
      }
    }
    } else if (orderType === 3) {
    if (stock > 0) {
      console.log("普通购买, 无优惠券");
    } else {
      console.log("手机库存不足");
    }
    }
    };
    order(1, true, 500); // 输出: 500 元定金预购, 得到 100 优惠券

//优化 var order500 = function (orderType, pay, stock) { if (orderType === 1 && pay === true) { console.log("500 元定金预购,得到 100 优惠券"); } else { return "nextSuccessor"; // 我不知道下一个节点是谁,反正把请求往后面传递 } }; var order200 = function (orderType, pay, stock) { if (orderType === 2 && pay === true) { console.log("200 元定金预购,得到 50 优惠券"); } else { return "nextSuccessor"; // 我不知道下一个节点是谁,反正把请求往后面传递 } }; var orderNormal = function (orderType, pay, stock) { if (stock > 0) { console.log("普通购买,无优惠券"); } else { console.log("手机库存不足"); } }; var Chain = function( fn ){ this.fn = fn; this.successor = null; }; Chain.prototype.setNextSuccessor = function( successor ){ return this.successor = successor; }; Chain.prototype.passRequest = function(){ var ret = this.fn.apply( this, arguments ); if ( ret === 'nextSuccessor' ){ return this.successor && this.successor.passRequest.apply( this.successor, arguments ); } return ret; }; //现在我们把 3 个订单函数分别包装成职责链的节点: var chainOrder500 = new Chain( order500 ); var chainOrder200 = new Chain( order200 ); var chainOrderNormal = new Chain( orderNormal ); //然后指定节点在职责链中的顺序: chainOrder500.setNextSuccessor( chainOrder200 ); chainOrder200.setNextSuccessor( chainOrderNormal ); //最后把请求传递给第一个节点: chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券 chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券 chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券 chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足


#### 中介者模式
中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的
相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知
中介者对象即可。
#### 装饰者模式
给对象动态地增加职责的方式称为装饰者(decorator)模式。装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责
代理模式和装饰者模式最重要的区别在于它们的**意图**和设计目的。代理模式的目的是,当直
接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代
理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。装饰者模式的作用就是为对
象动态加入行为。换句话说,代理模式强调一种关系(Proxy 与它的实体之间的关系),这种关系
可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确
定对象的全部功能时。代理模式通常只有一层代理本体的引用,而装饰者模式经常会形成一条
长长的装饰链。
#### 状态模式
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。
策略模式和状态模式的相同点是,它们都有一个上下文、一些策略或者状态类,上下文把请
求委托给这些类来执行。
但它们的**意图**不同,它们之间的区别是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,
所以客户必须熟知这些策略类的作用,以便客户可以随时主动切换算法;而在状态模式中,状态
和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情
发生在状态模式内部。对客户来说,并不需要了解这些细节。这正是状态模式的作用所在。
#### 适配器模式
适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本
由于接口不兼容而不能工作的两个软件实体可以一起工作。比如对不兼容的数据和方法进行封装,使得对外暴漏的数据和方法表现得一致

1. 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协同作用。
2. 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理模式是为了控制对对象的访问,通常也只包装一次。
3. 外观模式的作用倒是和适配器比较相似,有人把外观模式看成一组对象的适配器,但外观模式最显著的特点是定义了一个新的接口
### 模式
#### 单一职责原则
单一职责原则(SRP)的职责被定义为“引起变化的原因”。如果我们有两个动机去改写一
个方法,那么这个方法就具有两个职责。每个职责都是变化的一个轴线,如果一个方法承担了过
多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。
SRP 原则体现为:一个对象(方法)只做一件事情。
设计模式中的 SRP 原则:如代理模式、迭代器模式、单例模式和装饰者模式
#### 最少知识原则
最少知识原则(LKP)说的是一个软件实体应当尽可能少地与其他实体发生相互作用。
最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不
必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对
象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三
者对象来转发这些请求。
设计模式中的最少知识原则:体现得最多的中介者模式和外观模式
#### 开放-封闭原则
开放封闭原则(OCP)是最重要的一条原则。它的定义如下:
**软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改**
设计模式中的开放-封闭原则:几乎所有的设计模式都是遵守开放封闭原则的,开放-封闭原则是编写一个好程序的目标,其他设计原则都是达到这个目标的过程