【JavaScript学习总结】【13】、设计模式

设计模式(策略)

这次我们讲的设计模式叫做 Strategy, 策略模式
策略这个名字听上去,就像是预谋好的。也就是说,所有的解决方案提前设定好
不再根据当下的情形做判断,而是将不同问题与不同的解决方案对应起来。
举个简单的例子
比如,周一到周三,你上早班,早上要定闹铃,大概7点钟
周四到周五,晚班,闹铃可以晚一些,订在9点半
而周六是不用上班的,所以闹铃取消。
正常情况下,我们会在每天晚上判断一下明天上什么班,然后再去订闹铃。
但是现在的智能手机,已经使用策略模式帮我们解决了问题:
周一(早班)  ------>   7:00
周二(早班)  ------>   7:00
周三(早班)  ------>   7:00
周四(晚班)  ------>   9:30
周五(晚班)  ------>   9:30
周六(休息)  ------>   无
周日(休息)  ------>   无
(上图为锤子手机设置闹铃截图,每个闹铃都可以分别设定日期或跟随节假日自动调整, @罗永浩 请把广告费用结算一下)
所以每当周一的时候,系统自动调取周一对应的闹铃方案,也就是7:00的闹铃
所以,策略模式最大的好处,就在于将问题和方案进行了映射关系处理
这样以后就省去了 “这个问题该用哪个方案”的判断
代码:
var TankFactory = {
    tanklist : {
        "幻影" : function (){
             //......
        },
        "光棱" : function (){
             //......
        },
        "爱国者" : function (){
             //......         
        },
        "犀牛" : function (){
             //......         
        },
        "天启" : function (){
             //......           
        }
    },
    create : function ( type ){
        return new this .tanklist[ type ]();
        //大家注意这个地方tanklist[type]的使用,就是一种策略模式
    }   
};
再比如:
我们经常会遇到多分支判断的情况
function start ( level ){
       switch (level){
             case 1 : {
                  todo1();  break ;
            },
             case 2 : {
                  todo2();  break ;
            },
             case 3 : {
                  todo3();  break ;
            },
             case 4 : {
                  todo4();  break ;
            },
      }
}
我们可以将代码改造如下:
var start = ( function (){
     var todolist = {
         1 : function (){
             //todo1....
         },
         2 : function (){
             //todo2....
         },
         3 : function (){
             //todo3....
         },
         4 : function (){
             //todo4....
         }
     }     
     return function ( level ){
         todolist[ level ](); //省去了判断,直接调用对应函数
     }
})(); 
实战练习: 表单验证
HTML代码
< form action = "www.baidu.com"; method = "post" >
    < p >
       < label >用户名:</ label >
       < input type = "text" name = "username" />
    </ p >
    < p >
       < label >密码:</ label >
       < input type = "text" name = "password" />
    </ p >
    < p >
       < label >邮箱:</ label >
       < input type = "text" name = "email" />
    </ p >
    < p >
       < input type = "submit" value = "提交" />
    </ p >
</ form >
验证工具
var Validator = ( function (){
      var rules = {
           notEmpty : function ( val ){
                 return val != "" && val != null && val != undefined ;
           },
           lengthRange : function ( val , args ) {
                 var [min,max] = [args.split( "," )[ 0 ], args.split( "," )[ 1 ]];
                 return val.length >= min && val.length <= max;
           },
           email : function ( val ){
                 return /^\w+@\w+(\.\w+)*$/ .test(val);
           }
     }
      return {
           check : function ( val , sets ){
                 return sets. every ( function ( rulename ){
                      return rules[rulename.split( "\|" )[ 0 ]](val, rulename.split( "\|" )[ 1 ]);
                });
           }
     };
})();
使用验证工具校验表单
var form = document .querySelector( "#regForm" );
form. onsubmit = function (){
      try {
            var res = Validator.check(form.username.value,[ "notEmpty" , "lengthRange|6,20" ]);
            console .log(res);
     } catch (e) {
            console .log(e);
     }
      return false ;
}

设计模式(代理)

这次我们讲的设计模式叫做 Proxy, 代理模式
那么代理模式是什么呢?   举个例子
明星----》经纪人----》代理与被代理, 拍电影,真人秀 (谈判、行程安排、炒作)
代码:
function SuperStar(){
    this.拍电影= function(){
    }
    this.真人秀= function(){
    }
}
function Proxy(){
    this.主人 = new SuperStart();
    this.找我主人拍电影 = functioin(){
        //谈价格、谈档期、谈待遇、确定行程计划
        if(一切OK){
            this.主人.拍电影();    
        }
    }
    this.找我主人演真人秀 = function(){
        //谈价格、谈档期、谈待遇、确定行程计划
        if(一切ok){
            this.主人.真人秀();
        }
    }
}
实战练习:  加载图片时的延迟,我们希望真正的图片在加载完成之前都显示一个类似loading的图片,
当真的图片完成下载后,再替换掉loading图片
//非代理模式
function CreateImage(){
    var img = document.create("img");
    document.body.appendChild(img);
    img.src = "loading.jpg"; //loading图片
    
    var cacheImg = new Image();
    cacheImg.onload = function(){
        img.src = this.src;//当缓存对象加载完成后,将真正的图片放入img
    }
    this.setSrc = function(url){
        cacheImg.src = url; //使用缓存对象加载真正的图片
    }
}
这样做虽然可以达到效果,但明显CreateImage函数过于复杂,所以现在要简化函数的内容
让CreateImage职责更单一,只负责加载图片。就可以使用代理模式
代码:
function CreateImage(){
    var img = document.create("img");
    document.body.appendChild(img);
    img.src = "loading.jpg";
    this.setSrc = function(url){
        this.src = url; 
    }        
}
//代理
function ProxyImage(){
    var target = new CreateImage();
    var img = new Image();
    img.onload = function(){
        target.src = this.src;
    }
    this.setSrc = function(url){    
        img.src = url;
    }
}
var img = new ProxyImage();
img.setSrc = "realimg.jpg";
实战练习: 如何给一个ajax请求增加缓存?
假设现在有一个ajax请求函数:
function ajax(type, url, success){
    var req = new XMLHttpRequest();
    req.open(type, url, sync);
    req.onreadystatechange = function(){
        if(req.status == 200 && req.readyState == 4){
            success.call(null, req.responseText);    
        }
    }
    req.send();
}
增加缓存机制,每个请求过的ajax,5分钟内再次请求,从缓存中直接获取数据不再像服务器发送请求。
var proxyAjax = (function(){
    var cache = {};
    return function(type, url, success){
        //判断缓存是否存在,存在则直接返回
        if(!!cache[url])  return cache[url];
        //调用ajax函数,在请求结束时,将请求的数据先缓存起来
        ajax(type, url, function(data){
            cache[url] = data;
            setTimeout(function(){ //设置该url的缓存时间为5分钟
                cache[url] = undefined;
            },300*1000);
            //调用真正的回调函数
            success(data);
        });
    }
})();
这就是代理模式,以上。

设计模式(单例)

这次我们讲的设计模式叫做 Singleton, 单例模式
那么单例模式是什么呢?   举个简单的例子
笔记本电脑-----》使用简单-----》结构复杂-----》禁止自私拆卸,  隐藏复杂细节,不希望用户修改内容。
代码:
var macbook = {
    price : 9999,
    .....
}
var macbook = (function(){
    var price = 9999;  //私有属性,不可更改
    var power = "38.4v";
    var memery = {};
    return { 
        getPrice : function(){
            return price;
        }
    }
})();
macbook对象是单一的,并且它包含了一些我们不可修改的属性
这样的封装确实隐藏了细节,同时变得更安全。
但是,这并不是标准的单例模式。
单例模式有个很重的定义:一个类只能创建一个实例,也就是说多次实例化将会得到相同的结果。
代码:
var MacBook = function(){
    var price = 9999;  //私有属性,不可更改
    var power = "38.4v";
    var memery = {};
    this.getPrice = function(){
        return price;
    }    
}
var macbook01 = new MacBook();
var macbook02 = new MacBook();
console.log(macbook01 == macbook02);  //false
两次实例化对象并不相等,没有达到单例的效果
我们改造一下代码:
var MacBook = (function(){
    var instance = null;
    return function(){
        var price = 9999;
        this.getPrice = function(){
            retrun price;
        }
        instance = this;
    }
})();
//等价于我们得到了这样的构造函数
var MacBook = function(){
    var price = 9999;
    this.getPrice = function(){
        return price;
    }
    instance = this;
}
//这样并不能保证是单例的,但我们只要加上一个判断
var MacBook = function(){
    if(!!instance) return instance;
    var price = 9999;
    this.getPrice = function(){
        return price;
    }
    instance = this;
}
实战练习:  设计一个函数,创建一个弹出窗口。窗口可以反复打开和关闭。
function Popup(info){
    var div = document.createElement("div");
    div.innerHTML = info;
    document.body.appendChild(div);
    //隐藏div
    this.hide = function(){
        div.style.display = "none"; 
    }
}
分析:如果每次打开窗口都创建新的元素,是否浪费操作?
是否只需要一个DIV就可以反复使用? 那么可以用单例模式解决吗?
如何保证 new Popup("aaa") === new Popup("bbb"); //true
改造代码:
function Popup(info){
    if(!!Popup.instance){
        Popup.instance.show();
        return;
    }
    //原有代码部分全部保留
    this.show = function(){
        div.style.display = "block";
    }
    Popup.instance = this;
}
单例模式的其他好处:跨作用域共享
function A(){
    var c = new C();
    c.prop = xxxx;  //修改了c
}
function B(){
    var c = new C();  //如何获取c,并得到最新prop属性
}
//单例
function C(){
    if(!!C.ins) return C.ins;
    
    //.....
    
    C.ins = this;
}

设计模式(工厂)

这次我们讲的设计模式叫做 Factory, 工厂模式
那么工厂模式是什么呢?   
所谓工厂,当然是生产内容的地方。 这里有一个重要的概念请大家记住: 工厂生产的,一般是一个完整的产品。
如果是一个半成品,需要我们后续加工,那工厂模式的意义就没有了。
举个简单的例子
手机工厂,生产的各种各样的手机,当你把产品拿到手以后,你可以直接使用
并不需要知道这台手机是如何生产的。手机工厂生产出来的手机在出厂时都是一模一样的。
我们玩 RedAlert( 红色警戒 这款即时战略游戏时,一定会建造一个坦克工厂。
就像这样:
这个工厂里可以生产若干类型的坦克,比如幻影坦克,光棱坦克,爱国者导弹车等。
我们想得到一辆坦克,代码大概是这样的:
var tank01 = TankFactory.create("幻影");
var tank02 = TankFactory.create("光棱");
我们不需要知道坦克的创建过程。 坦克工厂的内部结构大概是这样:
var TankFactory = {
    tanklist : {
        "幻影" : function(){ //...... },
        "光棱" : function(){ //...... }
    },
    create : function(type){
        return new this.tanklist[type]();
    }    
};
当然,tanklist是不能随便让别人修改的,需要做成私有变量,于是改造一下代码
var TankFactory = (function(){
    var tanklist = {
        "幻影" : function(){ //...... },
        "光棱" : function(){ //...... }        
    }
    return {
        create : function(type){
            return new tanklist[type]();
        }
    };
})();
一个典型的工厂模式就是:
var oDiv = document.createElement("div");
var oSpan = document.createElement("span");
好处有两点:
第一,可以比较容易的批量化生产
第二,封装生产过程,让生产变得容易
当遇到一个构建起来比较复杂的对象时,就比较适合用工厂模式。
因此,往大了说,其实每个构造函数都是一个工厂。
例如:
var obj = new Object();
var now = new Date();
实际上,这些都是工厂,它们都可以批量生产,并且隐藏了复杂的构建细节。
实战应用:
著名的jQUeryUI框架中,有这样一个功能:
$("#dia").dialog();
如此简单的一句话,便可以生成一个经过美化的弹出层。
我们下面来自己写一个简单的DOM工厂。
var DomFactory = (function(){
    var domlist = {
        "LINK" : function(){
            return document.createElement("a");
        },
        "BTN" : function(){
            return document.createElement("button");
        },
        "IMG" : function(){
            return document.createElement("img");
        }
    }
    return {
        create: function(type){
            return new domlist[type]();
        }
    };
})();
//使用的时候像这样:
var link = DomFactory.create("LINK");
link.href = "http://www.xxxxxx";
不过这种写法可能跟平时用起来好像差别不大;
var link = document.createElement("a");
下面我们把这个工厂,改造一下,做一些复杂的事情。
var DomFactory = (function(){
    var domlist = {
        "LINKIMG" : function(href, src){
            var link = document.createElement("a");
            //去掉默认下划线
            link.style["text-decoration"] = "none";
            link.href = href;  
            var img = document.createElement("img");
            img.src = src;
            link.appendChild(img);
            return link;
        },
        "LINKBTN" : function(href, text){
            var link = document.createElement("a");
            //去掉默认下划线
            link.style["text-decoration"] = "none";
            link.href = href;  
            var btn = document.createElement("button");
            btn.innerHTML = text;
            link.appendChild(btn);
            return link;
        }
    }
    return {
        create: function(type){
            return new domlist[type](...Array.from(arguments).slice(1));
        }
    };
})();
使用方法如下:
var linkbtn = DomFactory.create("LINKBTN", "http://www.china.com", "中国");
这样一来我们便得到了一个由超链接包裹着的按钮了。
这就是工厂方法带给我们的好处。

设计模式(适配器)

这次我们讲的设计模式叫做 Adapter, 适配器模式
所谓适配,就是将两个不兼容的东西变得兼容起来。
举如:   我们的笔记本电脑,接收的电压通常在30~50V之间。
可是国内的通用电压都是220V, 因此我们的笔记本电脑无法直接使用墙插上的电压
所以笔记本电源线就是一个适配器
 通过  适配

再比如,苹果笔记是没有办法直接连接投影仪的。
因为苹果笔记本的接口是长这样的(下图),我们管它叫雷电接口

然而投影仪的接口长这样(下图),我们管它叫VGA接口

所以我们需要一个适配器(下图)

适配器还有一种应用场景,在平时也很常见。
比如你现在有这么一个需求, 你的好友喜得贵子,某天你要去探望,结果到了家门口发现忘了买礼物(随礼是中国人的陋习,不提倡)
不过好在身上有1000块,于是你就想干脆直接给钱吧,这样更实在。
可是!  难道直接这样给么?
这样太不雅观了,给人一种不礼貌不认真的感觉。主要是这样对方不好意思要。
于是你去超市,花1块钱买了个红包,就像这样
这下看起来好多了有没有?!   好了, 这个红包,就叫做适配器。它对我们的钱进行了包装,让对方可以欣然接受。
编程当中,我们更多的是采用这个红包的例子。
比如:
function IPHONE(){
    this.phonecall = function(){
      console.log("iphone打电话...");
    };
    this.playgame = function(){
      console.log("iphone玩游戏...");
    };
}
function IPAD(){
    this.playgame = function(){
      console.log("ipad玩游戏...");
    };
}
var iphone = new IPHONE(); //生产了一台手机
var ipad = new IPAD(); //生产了一台平板
//在出厂前,要对产品进行测试。 注意,ipad只能玩游戏,不能打电话
//这是测试工具
function test(target){
    try{
        target.playgame();
    } catch(e){
        console.error("玩游戏功能测试失败!");
    }
    try{
        target.phonecall();
    } catch(e){
        consol.error("打电话功能测试失败!");
    }
}
//开始测试
test(iphone);
test(ipad);
结果发现,ipad在测试phonecall(打电话)功能时,出现失败!
因为ipad原本在设计的时候,就不具备通话功能,但是要想经过这道测试工序,我们就必须想点办法了
问题描述:   
1,test要接受一个产品
2,我们手里有一个ipad
3,ipad不太符合test的需求,会导致不应该出现的错误
4,想办法在不修改ipad前提下,让ipad通过测试
这非常符合我们刚才所说的不兼容的场景, 于是,适配器模式要出现了!!
我们首先做一个适配器(红包),然后将ipad(钱)包装起来。
function Adapter(target){
    this.playgame = function(){
        if(!!target.playgame && typeof target.playgame === "function"){
            target.playgame();
        } else {
            console.log("该产品目前不支持playgame功能");
        }
    }
    this.phonecall = function(){
        if(!!target.phonecall && typeof target.phonecall === "function"){
            target.phonecall();
        } else {
            console.log("该产品目前不支持phonecall功能");
        }
    }
}
var  ipad_apt = new Adapter(new IPAD());
test(ipad_apt); 
//测试通过!
实战练习:
Math.min方法用来求最小值,但很可惜它只接收2个参数,多余参数会被忽略
于是我们做个适配器包装一下,让它可以接收任意多个参数
var MathAdapter = {
    min : function(){
        Math.min.apply(window.Math, arguments);
    }
}
Math.min(5,4,3,2,1);  //返回4
MathAdapter.min(5,4,3,2,1);  //返回1

设计模式(责任链)

这次我们讲的设计模式叫做Chain of responsbility, 责任链模式
所谓责任链,即是把责任拆分到多个人身上,然后各自履行各自的责任即可
举个例子:
我们要生产一件衣服,大概需要四个工序: 织布、设计、剪裁、缝合
那么接下来,我需要将准备好的原材料交给张三。
张三擅长织布,但不懂得其他工序,不过没关系,我告诉张三,他只管织布,然后把织好的布料交给李四就行,剩下的就不用管了。
李四是设计师,但不会裁缝,不过没关系,我告诉李四,他只需要根据布料款式设计就好,然后把设计图交给王五就行。
王五擅长测量剪裁,他把衣服剪裁好以后,就交给赵六
赵六最后通过缝合制作成衣。
你看,在这个过程中,每个人的职责都更单一了,大家分工协作,只专注做好自己分内的事情
假设我们有一系列处理字符串的工具,我们希望按照需求来定制我们的处理器。
目前有 filterA、filterB、filterC三个处理器
我们希望每个过滤器职责单一,只负责处理一种功能
JS代码
function filterA ( str ){
     str = str.toLowerCase()
      return filterB(str); //过滤器A完成任务,把剩下的职责交给B
}
function filterB ( str ){
     str[ 0 ] = str[ 0 ].toUpperCase();
      return filterC(str); //过滤器B完成任务,把剩下的职责交给C
}
function filterC ( str ){
     str = str.substring( 0 , 50 ) + "..." ;
      return str;
}
上面的代码,做到了职责单一的功能,并且大家形成一个链条,按顺序执行。
但是!这样做代码的耦合性太强了,A调用了B,B调用了C,代码之间产生了联系,就意味他们不是独立的,或者说没有办法独立存在!
于是我们把过滤器改造成独立功能,消除耦合
function filterA ( str ){ //全体字母变小写
      return  str ;
}
function filterB ( str ){ //首字母变大写
      return  str ;      
}
function filterC ( str ){ //超过50个字符后,用...替换
      return  str ;     
}
var filterChain = {
    chain : [filterA, filterB, filterC],   //这里可以设定我们需要的过滤器
    doHandle : function ( data , index ){
         //已经超出边界,则链条遍历完毕,返回
         if (index == chain.length) return
         //如果是第一次调用,则index为0
         index == undefined ? index = 0 : "donothing" ;
         //取出责任链中第一个处理函数,处理并返回数据
         var resdata =  this . chain[index](data);
         //将处理后的数据返回给下一个
         this .doHandle(resdata, index + 1 );
     }
};
filterChain.doHandle(str);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值