javascript设计模式之定义及案例

高阶函数是指至少满足下列条件之一的函数:

1) 函数可以作为参数被传递
2) 函数可以作为参数输出。

AOP:

1)AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。

单一职责原则:

单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。

单例模式的定义是:

1)保证一个类仅有一个实例
2)并提供一个访问它的全局访问点

var a={}
//或者
var Singleton = function( name ){
  this.name = name;
  this.instance = null;
};
Singleton.prototype.getName = function(){
  alert ( this.name );
};
Singleton.getInstance = function( name ){
  if ( !this.instance ){
    this.instance = new Singleton( name );
  }
  return this.instance;
};
var a = Singleton.getInstance( 'sven1' );
var b = Singleton.getInstance( 'sven2' );
alert ( a === b ); // true



    



虽然现在达到了惰性的目的,但失去了单例的效果。当我们每次点击登录按钮的时候,都会创建一个新的登录浮窗div。虽然我们可以在点击浮窗上的关闭按钮时(此处未实现)把这个浮窗从页面中删除掉,但这样频繁地创建和删除节点明显是不合理的,也是不必要的。




    



1)这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在createLoginLayer对象内部。
2)如果我们下次需要创建页面中唯一的iframe,或者script 标签,用来跨域请求数据,就必须得如法炮制,把createLoginLayer 函数几乎照抄一遍
现在我们就把如何管理单例的逻辑从原来的代码中抽离出来,这些逻辑被封装在getSingle函数内部,创建对象的方法fn 被当成参数动态传入getSingle 函数:

var getSingle = function( fn ){
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
};



    
 




策略模式:

定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
策略模式是一种常用且有效的设计模式,可以用于计算奖金、缓动动画、表单校验等例子来加深大家对策略模式的理解。
下面是策略模式的几个有点:
1)策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
2) 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy 中,使得它们易于切换,易于理解,易于扩展。
3) 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
4) 在策略模式中利用组合和委托来让Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

var strategies = {
  "S": function( salary ){
      return salary * 4;
  },
  "A": function( salary ){
      return salary * 3;
  },
  "B": function( salary ){
      return salary * 2;
  }
};
var calculateBonus = function( level, salary ){
  return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000

代理模式:

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景。比如,明星都有经纪人作为代理。如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演出的细节和报酬都谈好之后,再把合同交给明星签。代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
在JavaScript 开发中最常用的是虚拟代理和缓存代理:

//虚拟代理
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');
//缓存代理
var mult=function(){
  console.log("开始计算乘积");
  var a=1;
  for(var i=0;i

输出结果:

开始计算乘积
24
24
开始计算乘积
10
10

迭代器模式:

指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

var each = function (ary, callback) {
    for (var i = 0, l = ary.length; i < l; i++) {
      if (callback(i, ary[i]) === false) { // callback 的执行结果返回false,提前终止迭代
        break;
      }
    }
  };
  each([1, 2, 3, 4, 5], function (i, n) {
    if (n > 3) { // n 大于3 的时候终止循环
      return false;
    }
    console.log(n); // 分别输出:1, 2, 3
  });

输出结果:

1
2
3

发布—订阅模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript 开发中,我们一般用事件模型来替代传统的发布—订阅模式。

var Events = {
    clientList: {},
    listen: function (key, fn) {
      var fns = this.clientList[key]
      if (!fns) {
        fns = this.clientList[key] = [];
      }
      if (fn) {
        fns.push(fn);
      }
    },
    trigger: function () {
      var key = [].shift.call(arguments);
      var fns = this.clientList[key];
      if (!fns || fns.length == 0) {
        return false;
      }
      for (var i = 0, ary; ary = fns[i++];) {
        ary.apply(this, arguments);
      }
    },
    remove: function (key, fn) {
      var fns = this.clientList[key];
      if (!fns || fns.length == 0) {
        return false;
      }
      if (!fn) {
        fns.length = 0;
      } else {
        for (var i = 0; i < fns.length; i++) {
          if (fns[i] == fn) {
            fns.splice(i, 1);
          }
        }
      }
    }
  }
   var header = (function () { // header 模块
    Events.listen('loginSucc', function (data) {
      header.setAvatar(data.avatar);
    });
    return {
      setAvatar: function (data) {
        console.log(data+'设置header 模块的头像');
      }
    }
  })();
  var nav = (function () { // nav 模块
    Events.listen('loginSucc', function (data) {
      nav.setAvatar(data.avatar);
    });
    return {
      setAvatar: function (data) {
        console.log(data+'设置nav 模块的头像');
      }
    }
  })();
//用setTimeout模拟真实操作
  setTimeout(function(){
    Events.trigger('loginSucc',{avatar:"登录成功开始"});
  },3000);
  //下面是真实项目请求后操作
  // $.ajax('http:// xxx.com?login', function (data) { // 登录成功
    // login.trigger('loginSucc', data); // 发布登录成功的消息
  // });

输出结果:

登录成功开始设置header 模块的头像
登录成功开始设置nav 模块的头像
//也可以先发布,后订阅下面是简单js部分
var Events = {
    clientList: {},
    cache: {},
    listen: function (key, fn) {
      var fns = this.clientList[key]
      if (!fns) {
        fns = this.clientList[key] = [];
      }
      if (fn) {
        fns.push(fn);
      }
      if (JSON.stringify(this.cache) != "{}") {
        this._trigger(key, fn);
        this.cache = [];
      };

    },
    _trigger: function (key, fn) {
      var arys = this.cache[key];
      if (!fn || fn.length == 0) {
        return false;
      }
      for (var i = 0, ary; ary = arys[i++];) {
        fn.apply(this, ary);
      }
    },
    trigger: function () {
      var key = [].shift.call(arguments);
      var fns = this.clientList[key];
      if (typeof ss != "function" || typeof ss != "undefined") {
        if (!this.cache[key]) {
          this.cache[key] = [];
        }
        this.cache[key].push(arguments);
      }
      if (!fns || fns.length == 0) {
        return false;
      }
      for (var i = 0, ary; ary = fns[i++];) {
        ary.apply(this, arguments);
      }
    },
    remove: function (key, fn) {
      var fns = this.clientList[key];
      if (!fns || fns.length == 0) {
        return false;
      }
      if (!fn) {
        fns.length = 0;
      } else {
        for (var i = 0; i < fns.length; i++) {
          if (fns[i] == fn) {
            fns.splice(i, 1);
          }
        }
      }
    }
  }
  Events.trigger('click', 1);
  Events.listen('click', function (a) {
    console.log(a); // 输出:1
  });

输出结果:

1

命令模式

命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品,命令模式在JavaScript 语言中是一种隐形的模式
命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系
1)简单的命令模式案例:

//html



//不是命令模式实现
 var bindClick = function (button, func) {
    button.onclick = func;
  };
  var MenuBar = {
    refresh: function () {
      console.log('刷新菜单界面');
    }
  };
  var SubMenu = {
    add: function () {
      console.log('增加子菜单');
    },
    del: function () {
      console.log('删除子菜单');
    }
  };
  bindClick(button1, MenuBar.refresh);
  bindClick(button2, SubMenu.add);
  bindClick(button3, SubMenu.del);
//命令模式实现
   var MenuBar = {
    refresh: function () {
      console.log('刷新菜单目录');
    }
  };
  var SubMenu = {
    add: function () {
      console.log('增加子菜单');
    },
    del: function () {
      console.log('删除子菜单');
    }
  };
  var setCommand = function (button, command) {
    button.onclick = function () {
      command.execute();
    }
  };
  var RefreshMenuBarCommand = function (receiver) {
    this.receiver = receiver;
  };
  RefreshMenuBarCommand.prototype.execute = function () {
    this.receiver.refresh();
  };
  var AddSubMenuCommand = function (receiver) {
    this.receiver = receiver;
  };
  AddSubMenuCommand.prototype.execute = function () {
    this.receiver.add();
  };
  var DelSubMenuCommand = function (receiver) {
    this.receiver = receiver;
  };
  DelSubMenuCommand.prototype.execute = function () {
    console.log('删除子菜单');
  }
  var button1 = document.getElementById('button1'),
    button2 = document.getElementById('button2'),
    button3 = document.getElementById('button3');
  var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
  var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
  var delSubMenuCommand = new DelSubMenuCommand(SubMenu);
  setCommand(button1, refreshMenuBarCommand);
  setCommand(button2, addSubMenuCommand);
  setCommand(button3, delSubMenuCommand);

2)撤消和重做
很多时候,我们需要撤销一系列的命令。比如在一个围棋程序中,现在已经下了10 步棋,我们需要一次性悔棋到第5 步。在这之前,我们可以把所有执行过的下棋命令都储存在一个历史列表中,然后倒序循环来依次执行这些命令的undo 操作,直到循环执行到第5 个命令为止。然而,在某些情况下无法顺利地利用undo 操作让对象回到execute 之前的状态。比如在一个Canvas 画图的程序中,画布上有一些点,我们在这些点之间画了N 条曲线把这些点相互连接起来,当然这是用命令模式来实现的。但是我们却很难为这里的命令对象定义一个擦除某条曲线的undo 操作,因为在Canvas 画图中,擦除一条线相对不容易实现。这时候最好的办法是先清除画布,然后把刚才执行过的命令全部重新执行一遍,这一点同样可以利用一个历史列表堆栈办到。记录命令日志,然后重复执行它们,这是逆转不可逆命令的一个好办法。

//html

//js
 var Ryu = {
    attack: function () {
      console.log("攻击");
    },
    defense: function () {
      console.log("防御");
    },
    jump: function () {
      console.log("跳跃");
    },
    crouch: function () {
      console.log("蹲下");
    }
  }
  var commands = {
    "119": "jump", // W
    "115": "crouch", // S
    "97": "defense", // A
    "100": "attack" // D
  };
  var commandStack=[];
  var makeCommand=function(receive,state){
    return function(){
      return receive[state]();
    }
  }
  document.onkeypress=function(e){
    var keyCode=e.keyCode;
    var command=makeCommand(Ryu,commands[keyCode]);
    if(command){
      command();
      commandStack.push(command);
    }
  }
  document.getElementById("replay").onclick=function(){
    var command=null;
    for(var i=0;i

宏命令

宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。

var closeDoorCommand = {
    execute: function () {
      console.log('关门');
    }
  };
  var openPcCommand = {
    execute: function () {
      console.log('开电脑');
    }
  };
  var openQQCommand = {
    execute: function () {
      console.log('登录QQ');
    }
  };

var MacroCommand=function(){
  return {
    commandsList:[],
    add:function(command){
      this.commandsList.push(command);
    },
    execute:function(){
      for(var i=0,command;command=this.commandsList[i++];){
        command.execute();
      }
    }
  }
}

var macroCommand = MacroCommand();
macroCommand.add( closeDoorCommand );
macroCommand.add( openPcCommand );
macroCommand.add( openQQCommand );
macroCommand.execute();

输出结果:

关门
开电脑
登录QQ

组合模式

组合模式将对象组合成树形结构,以表示“部分整体”的层次结构。 除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
1)提供了一种遍历树形结构的方案,通过调用组合对象的execute 方法,程序会递归调用组合对象下面的叶对象的execute 方法,所以我们的万能遥控器只需要一次操作,便能依次完成关门、打开电脑、登录QQ 这几件事情。组合模式可以非常方便地描述对象部分整体层次结构。
2)利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。

一些值得注意的地方

1)组合模式不是父子关系
组合模式的树型结构容易让人误以为组合对象和叶对象是父子关系,这是不正确的。组合模式是一种HAS-A(聚合)的关系,而不是IS-A。组合对象包含一组叶对象,但Leaf并不是Composite 的子类。组合对象把请求委托给它所包含的所有叶对象,它们能够合作的关键是拥有相同的接口。为了方便描述,本章有时候把上下级对象称为父子节点,但大家要知道,它们并非真正意义上的父子关系。
2)对叶对象操作的一致性
组合模式除了要求组合对象和叶对象拥有相同的接口之外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性。比如公司要给全体员工发放元旦的过节费1000 块,这个场景可以运用组合模式,但如果公
司给今天过生日的员工发送一封生日祝福的邮件,组合模式在这里就没有用武之地了,除非先把今天过生日的员工挑选出来。只有用一致的方式对待列表中的每个叶对象的时候,才适合使用组合模式。
3)双向映射关系
发放过节费的通知步骤是从公司到各个部门,再到各个小组,最后到每个员工的邮箱里。这本身是一个组合模式的好例子,但要考虑的一种情况是,也许某些员工属于多个组织架构。比如某位架构师既隶属于开发组,又隶属于架构组,对象之间的关系并不是严格意义上的层次结构,在这种情况下,是不适合使用组合模式的,该架构师很可能会收到两份过节费。这种复合情况下我们必须给父节点和子节点建立双向映射关系,一个简单的方法是给小组和员工对象都增加集合来保存对方的引用。但是这种相互间的引用相当复杂,而且对象之间产生了过多的耦合性,修改或者删除一个对象都变得困难,此时我们可以引入中介者模式来管理这些对象。
4) 用职责链模式提高组合模式性能
在组合模式中,如果树的结构比较复杂,节点数量很多,在遍历树的过程中,性能方面也许表现得不够理想。有时候我们确实可以借助一些技巧,在实际操作中避免遍历整棵树,有一种现成的方案是借助职责链模式。职责链模式一般需要我们手动去设置链条,但在组合模式中,父对象和子对象之间实际上形成了天然的职责链。让请求顺着链条从父对象往子对象传递,或者是反过来从子对象往父对象传递,直到遇到可以处理该请求的对象为止,这也是职责链模式的经典运用场景之一。


var openAcCommand = {
            execute: function () {
                console.log('打开空调');
            }
        };
        var openTvCommand = {
            execute: function () {
                console.log('打开电视');
            }
        };
        var openSoundCommand = {
            execute: function () {
                console.log('打开音响');
            }
        };
        var closeDoorCommand = {
            execute: function () {
                console.log('关门');
            }
        };
        var openPcCommand = {
            execute: function () {
                console.log('开电脑');
            }
        };
        var openQQCommand = {
            execute: function () {
                console.log('登录QQ');
            }
        };
        var MacroCommand = function () {
            return {
                commandsList: [],
                add: function (command) {
                    this.commandsList.push(command);
                },
                execute: function () {
                    for (var i = 0, command; command = this.commandsList[i++];) {
                        command.execute();
                    }
                }
            }
        }
        var macroCommand1 = MacroCommand();
        macroCommand1.add(openTvCommand);
        macroCommand1.add(openSoundCommand);
        var macroCommand2 = MacroCommand();
        macroCommand2.add(closeDoorCommand);
        macroCommand2.add(openPcCommand);
        macroCommand2.add(openQQCommand);
        var macroCommand = MacroCommand();
        macroCommand.add(openAcCommand);
        macroCommand.add(macroCommand1);
        macroCommand.add(macroCommand2);
        var setCommand = (function (command) {
            document.getElementById('button').onclick = function () {
                command.execute();
            }
        })(macroCommand);

输出结果:

打开空调
打开电视
打开音响
关门
开电脑
登录QQ

组合模式的例子——扫描文件夹

//js
var Folder = function (name) {
            this.name = name;
            this.files = [];
        }
        Folder.prototype.add = function (file) {
            this.files.push(file);
        }
        Folder.prototype.scan = function () {
            console.log("开始扫描文件夹1:" + this.name);
            for (var i = 0, file; file = this.files[i++];) {
                file.scan();
            }
        }
        var File = function (name) {
            this.name = name;
        }
        File.prototype.add = function (file) {
            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.scan();

输出结果:

开始扫描文件夹1:学习资料
开始扫描文件夹1:JavaScript
开始扫描文件夹:JavaScript 设计模式与开发实践
开始扫描文件夹1:jQuery
开始扫描文件夹:精通jQuery
开始扫描文件夹:重构与模式

引用父对象

组合对象保存了它下面的子节点的引用,这是组合模式的特点,此时树结构是从上至下的。但有时候我们需要在子节点上保持对父节点的引用,比如在组合模式中使用职责链时,有可能需要让请求从子节点往父节点上冒泡传递。还有当我们删除某个文件的时候,实际上是从这个文件所在的上层文件夹中删除该文件的。
现在来改写扫描文件夹的代码,使得在扫描整个文件夹之前,我们可以先移除某一个具体的文件。

var Folder = function (name) {
            this.name = name;
            this.parent=null;
            this.files = [];
        }
        Folder.prototype.add = function (file) {
            file.parent=this;
            this.files.push(file);
        }
        Folder.prototype.scan = function () {
            console.log("开始扫描文件夹1:" + this.name);
            for (var i = 0, file; file = this.files[i++];) {
                file.scan();
            }
        }
        Folder.prototype.remove=function(){
            if(!this.parent){
                return false;
            }else{
                for(var i=0,len=this.parent.files.length;i

输出结果:

开始扫描文件夹1:学习资料 
开始扫描文件夹1:jQuery
开始扫描文件夹:精通jQuery
开始扫描文件夹:重构与模式

模板方法模式

模板方法模式是一种只需使用继承就可以实现的非常简单的模式。
模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

var Beverage = function () { };
        Beverage.prototype.boilWater = function () {
            console.log('把水煮沸');
        };
        Beverage.prototype.brew = function () {
            throw new Error('子类必须重写brew 方法');
        };
        Beverage.prototype.pourInCup = function () {
            throw new Error('子类必须重写pourInCup 方法');
        };
        Beverage.prototype.addCondiments = function () {
            throw new Error('子类必须重写addCondiments 方法');
        };
        Beverage.prototype.customerWantsCondiments = function () {
            return true; // 默认需要调料
        };
        Beverage.prototype.init = function () {
            this.boilWater();
            this.brew();
            this.pourInCup();
            if (this.customerWantsCondiments()) { // 如果挂钩返回true,则需要调料
                this.addCondiments();
            }
        };
        var CoffeeWithHook = function () { };
        CoffeeWithHook.prototype = new Beverage();
        CoffeeWithHook.prototype.brew = function () {
            console.log('用沸水冲泡咖啡');
        };
        CoffeeWithHook.prototype.pourInCup = function () {
            console.log('把咖啡倒进杯子');
        };
        CoffeeWithHook.prototype.addCondiments = function () {
            console.log('加糖和牛奶');
        };
        CoffeeWithHook.prototype.customerWantsCondiments = function () {
            return window.confirm('请问需要调料吗?');
        };
        var coffeeWithHook = new CoffeeWithHook();
        coffeeWithHook.init();

输出结果:

把水煮沸
用沸水冲泡咖啡
把咖啡倒进杯子
加糖和牛奶

好莱坞原则:别调用我,我调用你

var Beverage = function (param) {
            var boilWater = function () {
                console.log('把水煮沸');
            };
            var brew = param.brew || function () {
                throw new Error('必须传递brew 方法');
            };
            var pourInCup = param.pourInCup || function () {
                throw new Error('必须传递pourInCup 方法');
            };
            var addCondiments = param.addCondiments || function () {
                throw new Error('必须传递addCondiments 方法');
            };
            var F = function () { };
            F.prototype.init = function () {
                boilWater();
                brew();
                pourInCup();
                addCondiments();
            };
            return F;
        };
        var Coffee = Beverage({
            brew: function () {
                console.log('用沸水冲泡咖啡');
            },
            pourInCup: function () {
                console.log('把咖啡倒进杯子');
            },
            addCondiments: function () {
                console.log('加糖和牛奶');
            }
        });
        var Tea = Beverage({
            brew: function () {
                console.log('用沸水浸泡茶叶');
            },
            pourInCup: function () {
                console.log('把茶倒进杯子');
            },
            addCondiments: function () {
                console.log('加柠檬');
            }
        });
        var coffee = new Coffee();
        coffee.init();
        var tea = new Tea();
        tea.init();

输出结果:

把水煮沸
用沸水冲泡咖啡
把咖啡倒进杯子
加糖和牛奶
把水煮沸
用沸水浸泡茶叶
把茶倒进杯子
加柠檬

享元模式

享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在JavaScript 中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非常有意义的事情。

1)内部状态存储于对象内部。
2) 内部状态可以被一些对象共享。
3) 内部状态独立于具体的场景,通常不会改变。
4) 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时
便可以使用享元模式:

1) 一个程序中使用了大量的相似对象。
2) 由于使用了大量对象,造成很大的内存开销。
3) 对象的大多数状态都可以变为外部状态。
4) 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

var Model = function (sex) {
            this.sex = sex;
        }
        Model.prototype.takePhoto = function () {
            console.log('sex=' + this.sex + 'underwear=' + this.underwear);
        }
        var maleModel = new Model('male'),
            femaleModel = new Model('female');
        for (var i = 1; i <= 50; i++) {
            maleModel.underwear = 'underwear' + i;
            maleModel.takePhoto();
        };

对象池实现

var objectPoolFactory = function (createObjFn) {
            var objectPool = [];
            return {
                create: function () {
                    var obj = objectPool.length === 0 ?
                        createObjFn.apply(this, arguments) : objectPool.shift();
                    return obj;
                },
                recover: function (obj) {
                    objectPool.push(obj);
                }
            }
        };
        var iframeFactory = objectPoolFactory(function () {
            var iframe = document.createElement('iframe');
            document.body.appendChild(iframe);
            iframe.onload = function () {
                iframe.onload = null; // 防止iframe 重复加载的bug
                iframeFactory.recover(iframe); // iframe 加载完成之后回收节点
            }
            return iframe;
        });
        var iframe1 = iframeFactory.create();
        iframe1.src = 'http:// baidu.com';
        var iframe2 = iframeFactory.create();
        iframe2.src = 'http:// QQ.com';
        setTimeout(function () {
            var iframe3 = iframeFactory.create();
            iframe3.src = 'http:// 163.com';
        }, 3000);

职责链模式

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

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 优惠券

但这远远算不上一段值得夸奖的代码。order 函数不仅巨大到难以阅读,而且需要经常进行修改。现在我们采用职责链模式重构这段代码。

// 500 元订单
    var order500 = function (orderType, pay, stock) {
        if (orderType === 1 && pay === true) {
            console.log('500 元定金预购, 得到100 优惠券');
        } else {
            order200(orderType, pay, stock); // 将请求传递给200 元订单
        }
    };
    // 200 元订单
    var order200 = function (orderType, pay, stock) {
        if (orderType === 2 && pay === true) {
            console.log('200 元定金预购, 得到50 优惠券');
        } else {
            orderNormal(orderType, pay, stock); // 将请求传递给普通订单
        }
    };
    // 普通购买订单
    var orderNormal = function (orderType, pay, stock) {
        if (stock > 0) {
            console.log('普通购买, 无优惠券');
        } else {
            console.log('手机库存不足');
        }
    };
    // 测试结果:
    order500(1, true, 500); // 输出:500 元定金预购, 得到100 优惠券
    order500(1, false, 500); // 输出:普通购买, 无优惠券
    order500(2, true, 500); // 输出:200 元定金预购, 得到500 优惠券
    order500(3, false, 500); // 输出:普通购买, 无优惠券
    order500(3, false, 0); // 输出:手机库存不足

目前已经有了不小的进步,但我们不会满足于此,虽然已经把大函数拆分成了互不影响的3个小函数,但可以看到,请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中,这依然是违反开放封闭原则的,如果有天我们要增加300 元预订或者去掉200 元预订,意
味着就必须改动这些业务函数内部。就像一根环环相扣打了死结的链条,如果要增加、拆除或者
移动一个节点,就必须得先砸烂这根链条。我们采用一种更灵活的方式,来改进上面的职责链模式,目标是让链中的各个节点可以灵活拆分和重组。

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;
    };
    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); // 输出:手机库存不足
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('手机库存不足');
        }
    };
    Function.prototype.after = function (fn) {
        var self = this;
        return function () {
            var ret = self.apply(this, arguments);
            if (ret === 'nextSuccessor') {
                return fn.apply(this, arguments);
            }
            return ret;
        }
    };
    var order = order500.after(order200).after(orderNormal);
    order(1, true, 500); // 输出:500 元定金预购,得到100 优惠券
    order(2, true, 500); // 输出:200 元定金预购,得到50 优惠券
    order(1, false, 500); // 输出:普通购买,无优惠券

中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。

function Player(name){
  this.name=name;
  this.enemy=null;
}
Player.prototype.win=function(){
  console.log(this.name+'won');
}
Player.prototype.lose=function(){
  console.log(this.name+'lose');
}
Player.prototype.die=function(){
  this.lose();
  this.enemy.win();
}
var player1 = new Player( '皮蛋' );
var player2 = new Player( '小乖' );
player1.enemy = player2;
player2.enemy = player1;
player1.die();// 输出:皮蛋 lost、小乖 won
var players = [];
  function Player(name, teamColor) {
    this.name = name;
    this.enemies = [];
    this.state = 'live';
    this.partners = [];
    this.teamColor = teamColor;
  }
  Player.prototype.win = function () {
    console.log(this.name + 'won');
  }
  Player.prototype.lose = function () {
    console.log(this.name + 'lose');
  }
  Player.prototype.die = function () {
    var all_dead = true;
    this.state = 'dead';
    for (var i = 0, play; play = this.partners[i++];) {
      if (play.state == 'live') {
        all_dead = false;
      }
    }
    if (all_dead) {
      this.lose();
      for (var i = 0, play; play = this.partners[i++];) {
        play.lose();
      }
      for (var i = 0, enemy; enemy = this.enemies[i++];) {
        enemy.win();
      }
    }
  }
  var playerFactory = function (name, teamColor) {
    var newPlay = new Player(name, teamColor);
    for (var i = 0, play; play = players[i++];) {
      if (play.teamColor == teamColor) {
        newPlay.partners.push(play);
        play.partners.push(newPlay);
      } else {
        newPlay.enemies.push(play);
        play.enemies.push(newPlay);
      }
    }
    players.push(newPlay);
    return newPlay;
  }
  var player1 = playerFactory('皮蛋', 'red'),
    player2 = playerFactory('小乖', 'red'),
    player3 = playerFactory('宝宝', 'red'),
    player4 = playerFactory('小强', 'red');
  //蓝队:
  var player5 = playerFactory('黑妞', 'blue'),
    player6 = playerFactory('葱头', 'blue'),
    player7 = playerFactory('胖墩', 'blue'),
    player8 = playerFactory('海盗', 'blue');
  player1.die();
  player2.die();
  player4.die();
  player3.die();

执行结果:


javascript设计模式之定义及案例_第1张图片



    选择颜色:
    
    选择内存:
    
    输入购买数量:
    
    
您选择了颜色:

您选择了内存:

您输入了数量:

javascript设计模式之定义及案例_第2张图片

装饰者模式

装饰者模式将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链依次传递到所有的对象,每个对象都有处理这条请求的机会。

var plane = {
        fire: function () {
            console.log('发射普通子弹');
        }
    }
    var missileDecorator = function () {
        console.log('发射导弹');
    }
    var atomDecorator = function () {
        console.log('发射原子弹');
    }
    var fire1 = plane.fire;
    plane.fire = function () {
        fire1();
        missileDecorator();
    }
    var fire2 = plane.fire;
    plane.fire = function () {
        fire2();
        atomDecorator();
    }
    plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
var a = function () {
        alert(1);
    }
    var _a = a;
    a = function () {
        _a();
        alert(2);
    }
    a();
//弹出1,弹出2
 window.onload = function () {
        alert(1);
    }
    var _onload = window.onload || function () { };
    window.onload = function () {
        _onload();
        alert(2);
    }
//弹出1,弹出2
 
        
        

    

注释:此时_getElementById 是一个全局函数,当调用一个全局函数时,this 是指向window 的,而document.getElementById 方法的内部实现需要使用this 引用,this 在这个方法内预期是指向document,而不是window


 Function.prototype.before = function (beforefn) {
        var __self = this; // 保存原函数的引用
        return function () { // 返回包含了原函数和新函数的"代理"函数
            beforefn.apply(this, arguments); // 执行新函数,且保证this 不被劫持,新函数接受的参数
            // 也会被原封不动地传入原函数,新函数在原函数之前执行
            return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,
            // 并且保证this 不被劫持
        }
    }
    Function.prototype.after = function (afterfn) {
        var __self = this;
        return function () {
            var ret = __self.apply(this, arguments);
            afterfn.apply(this, arguments);
            return ret;
        }
    };
    window.onload = function () {
        alert(1);
    }
    window.onload = (window.onload || function () { }).after(function () {
        alert(2);
    }).after(function () {
        alert(3);
    }).after(function () {
        alert(4);
    });







    用户名:
     密码:
    
    



注释:formSubmit 函数在此处承担了两个职责,除了提交ajax 请求之外,还要验证用户输入的合法性。这种代码一来会造成函数臃肿,职责混乱,二来谈不上任何可复用性。




    用户名:
     密码:
    
    




注释:现在的代码已经有了一些改进,我们把校验的逻辑都放到了validata 函数中,但formSubmit函数的内部还要计算validata 函数的返回值,因为返回值的结果表明了是否通过校验。
接下来进一步优化这段代码,使validata 和formSubmit 完全分离开来。首先要改写Function.prototype.before, 如果beforefn 的执行结果返回false,表示不再执行后面的原函数,代码如下:



    用户名:
     密码:
    
    



装饰者模式和第6 章代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求。代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。装饰者模式的作用就是为对象动态加入行为。换句话说,代理模式强调一种关系(Proxy 与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确定对象的全部功能时。代理模式通常只有一层代理本体的引用,而装饰者模式经常会形成一条长长的装饰链。
在虚拟代理实现图片预加载的例子中,本体负责设置img 节点的src,代理则提供了预加载的功能,这看起来也是“加入行为”的一种方式,但这种加入行为的方式和装饰者模式的偏重点是不一样的。装饰者模式是实实在在的为对象增加新的职责和行为,而代理做的事情还是跟本体一样,最终都是设置src。但代理可以加入一些“聪明”的功能,比如在图片真正加载好之前,

状态模式

状态模式是一种非同寻常的优秀模式,它也许是解决某些需求场景的最好方法。虽然状态模式并不是一种简单到一目了然的模式(它往往还会带来代码量的增加),但你一旦明白了状态模式的精髓,以后一定会感谢它带给你的无与伦比的好处。

你可能感兴趣的:(javascript设计模式之定义及案例)