详细分析JavaScript设计模式

 
说设计模式之前首先要先了解一下有什么设计原则,也就是你新写一个模式,要遵循什么样的原则。
一、设计原则
1、单一职责原则:也就是 一个对象或方法只做一件事情。尽可能的将对象划分成较小的粒度。
2、最少知识原则: 对象之间尽可能少进行通信,不要发生直接的交互,如果对象之间有需要进行通讯的,可以转交给第三方进行处理。
3、开放-封闭原则:实体(类、模块、函数)等应该是可以拓展等,但是 不可以修改,当需要改变一个程序等功能或者给这个程序增加新的功能等时候,可以使用增加代码的方式,尽量避免改变程序的源代码,保持系统的稳定性。
 
总的来说就是,一个对象就让它负责一件事情,并且不要修改它原有的代码
 
二、设计模式
我们总说设计模式设计模式,那么设计模式究竟是什么?我个人的理解就是解决方案的模版,遇到同样的业务场景可以使用同样的设计模式,这样我们写代码的时候可复用性就高了。
 
 
1、单例模式
故名思义,单例模式就是保证全局就只有一个实例,并且全局可以访问。
我以前做的一个项目里面,设计到蓝牙连接,这个蓝牙类在全局就只可以拥有一个实例,那么怎么实现单例呢?
首先,我们创建一个全局的对象,代表这个实例,然后判断实例是否已经创建了,如果是直接返回,如果没有调用这个类的构造函数,并赋值给这个实例,然后返回。
 
//1、使用代理实现单例模式,就是将管理单例的代码独立出来
function SetSingleton(callback){

var instance = null

return function(){

if(!instance){
instance = callback.apply(this,arguments)

}
return instance

}
}
//单例1:coder
function Setcode(code){

this.code = code

}
Setcode.prototype.getName = function(){

console.log('coder',this.code)
}
var Coder = SetSingleton(function(code){

//实例一个类
var code = new Setcode(code)

return code

})
Coder('1').getName()
Coder('2').getName()
Coder('3').getName()


//单例2 pmer
function SetPm(pm){

this.pm = pm

}
SetPm.prototype.getName = function(pm){

console.log('pmer',this.pm)
}
var Pmer = SetSingleton(function(pm){

var pm = new SetPm(pm)

return pm

})
Pmer('pm1').getName()
Pmer('pm2').getName()
Pmer('pm3').getName()

 

 
 
 
在JavaScript中,除了使用代理实现单例,我们还可以实现惰性单例。什么是惰性单例呢?就是这个实例,直到要使用它的时候,我才给他创建。
var getSingle = function( fn ){
    var result;
    return function(){
        return result || ( result = fn .apply(this, arguments ) );
    }
};

 

上面这个fn对应的是,需要管理单例的对象初始化的回调函数。比如我现在需要创建一个全局唯一的div,那么对应的就是
var createDiv = function(){
    var div = document.createElement( 'div' );
    div.innerHTML = '我是新创建的div';
    div.style.display = 'none';
    document.body.appendChild( div );
    return div;
};
var createSingleDiv = getSingle( createDiv);
document.getElementById( 'cteareDiv' ).onclick = function(){
    var div = createSingleDiv();
    div.style.display = 'block';
};

 

 
 
2、策略模式
什么是策略模式呢?策略模式指的是定义一系列的算法,把它们一个个封装起来。就是将算法的使用和算法的实现分离开来。一个基于策略模式的程序至少由两部分组成。第一个部分是 一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类context,context 接受客户的请求,随后把请求委托给某一个策略类
下面我们来慢慢分析,一个策略模式在JavaScript中如何实现
1、首先我们封装一个策略类,在js中我们无需定义一个类,直接使用一个对象来封装这个策略类
var strategies = {
    "S": function( salary ){
        return salary * 4;
    },
    "A": function( salary ){
        return salary * 3;
    },
    "B": function( salary ){
        return salary * 2;
    }
};

 

这个策略类中一共有三个具体的算法分别是S,A,B每个算法都有不一样的实现
2、接下来实现一个环境类context,用来接收客户的请求,并把请求根据需求委托给策略类
var calculateBonus = function( level, salary ){
    return strategies[ level ]( salary );
};

 

在这个环境类中接收两个参数,一个是等级,一个是工资,这个等级对应了策略类中不同的实现。
3、接下来就可以使用了
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000
console.log( calculateBonus( 'B', 10000 ) ); // 输出:20000

 

下面我们用策略类来写一个页面检测表单的
<html>
   
<body>
    <form action="http:// xxx.com/register" id="registerForm" method="post">
        请输入用户名:<input type="text" name="userName"/ >
        请输入密码:<input type="text" name="password"/ >
        请输入手机号码:<input type="text" name="phoneNumber"/ >
        <button>提交</button>
    </form>
</body>
<script>
/***********************策略对象**************************/
var strategies = {
    isNonEmpty: function( value, errorMsg ){
        if ( value === '' ){
            return errorMsg;
        }
    },
    minLength: function( value, length, errorMsg ){
        if ( value.length < length ){
            return errorMsg;
        }
    },
    isMobile: function( value, errorMsg ){
        if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
            return errorMsg;
        }
    }
};
/***********************Validator 类**************************/
var Validator = function(){
    this.cache = [];
};
Validator.prototype.add = function( dom, rules ){
    var self = this;
    for ( var i = 0, rule; rule = rules[ i++ ]; ){
        (function( rule ){
            var strategyAry = rule.strategy.split( ':' );
            console.log('strategyAry',strategyAry)
            var errorMsg = rule.errorMsg;
            self.cache.push(function(){
                // strategyAry是传给策略类的参数数组,第一个是输入的值,最后一个是错误信息,中间的保留传入的参数比如长度
                var strategy = strategyAry.shift();
                strategyAry.unshift( dom.value );
                strategyAry.push( errorMsg );
                console.log('strategyAry2',strategyAry)
                return strategies[ strategy ].apply( dom, strategyAry );
            });
        })( rule )
    }
};
Validator.prototype.start = function(){
    for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
        var errorMsg = validatorFunc();
        if ( errorMsg ){
            return errorMsg;
        }
    }
};
/***********************客户调用代码**************************/
var registerForm = document.getElementById( 'registerForm' );
var validataFunc = function(){
var validator = new Validator();
validator.add( registerForm.userName, [{
    strategy: 'isNonEmpty',
    errorMsg: '用户名不能为空'
    }, {
    strategy: 'minLength:6',
    errorMsg: '用户名长度不能小于 10 位'
    }]);
validator.add( registerForm.password, [{
    strategy: 'minLength:6',
    errorMsg: '密码长度不能小于 6 位'
}]);
validator.add( registerForm.phoneNumber, [{
    strategy: 'isMobile',
    errorMsg: '手机号码格式不正确'
}]);
    var errorMsg = validator.start();
    return errorMsg;
}
registerForm.onsubmit = function(){
    var errorMsg = validataFunc();
    if ( errorMsg ){
        alert ( errorMsg );
        return false;
    }
};
</script>
</html>
3、代理模式
我们在第一个单例模式的时候其实就有用到代理了,那么代理模式又是什么呢?这个代理其实就像中介,像我们在安居客上面 租房,我们首先接触到的不是真正的房东,而是那些地产中介,我们和地产中介进行联系以后,中介就会帮助我们去联系一个真正的房东。所以这里的中介就是一个代理
代理又分为保护代理和虚拟代理,其中保护代理是,在代理中可以拒绝掉一些对实体的请求,这样可以保证对实体的请求一定是可靠有用的。虚拟代理就是,将一些开销很大的对象,延迟到真正需要它的时候才去创建。必须图片预加载。
那么实现代理模式有什么需要遵循的原则呢?
首先,也是最重要的,代理和本体的接口要一致,这样客户不用知道你是代理还是本体,我请求的接口是一样的。
接下来我们用虚拟代理来合并请求。
在一些场合中,我们列表中的每个元素只要被点中就给服务器发送一个请求,如果我们可以再一秒中点击多个元素,那么就会有多个请求发送给服务器了,如果大量的这样操作,将会给服务器造成很大的压力。所以我们可以使用代理,来在一段时间内把若干个请求一起发送给服务器
<html>
    
<body>
    <input type="checkbox" id="1"></input>1
    <input type="checkbox" id="2"></input>2
    <input type="checkbox" id="3"></input>3
    <input type="checkbox" id="4"></input>4
    <input type="checkbox" id="5"></input>5
    <input type="checkbox" id="6"></input>6
    <input type="checkbox" id="7"></input>7
    <input type="checkbox" id="8"></input>8
    <input type="checkbox" id="9"></input>9
</body>
<script>
//异步请求
var syncRequest = function(id){
    console.log('我要给服务器发送请求啦',id)
}
//代理请求
proxySyncRequest = (function(id){
    var cache = [],timer
    return function(id){
        cache.push(id)
        if(timer){
            return
        }
        timer = setTimeout(function(){
            let id = cache.join(',')
            syncRequest(id)//发送多个id的请求
            clearTimeout(timer)//清除定时器
            timer = 0
            cache = []
        },2000)
    }
})()
var checkbox = document.getElementsByTagName('input')
for(let i = 0,c;c = checkbox[i++];){
    c.onclick = function(){
        if(this.checked == true){
            proxySyncRequest(this.id)
        }
    }
}


</script>
</html>

 

 
接下来,我们来实现一个缓存代理。
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
console.log('计算乘积',a)
return a;
};
var proxyMult = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = mult.apply( this, arguments );
}
})();
console.log(proxyMult( 1, 2, 3, 4 )); // 输出:24
console.log(proxyMult( 1, 2, 3, 4 )); // 输出:24

 

 
 
可以看到我们了两次参数一样,但是实际上只调用了一次。
接下来我们可以修改代码而使得这个代理可以代理多个操作
 
var mult = function(){
    var a = 1;
    for ( var i = 0, l = arguments.length; i < l; i++ ){
        a = a * arguments[i];
    }
    console.log('计算乘积',a)
    return a;
};
var createProxyFactory= function(fn){
    console.log(fn)
    var cache = {};
    return function(){
        var args = Array.prototype.join.call( arguments, ',' );
        if ( args in cache ){
        return cache[ args ];
         }
        return cache[ args ] = fn.apply( this, arguments );
    }
}
proxyMult = createProxyFactory(mult)
console.log(proxyMult( 1, 2, 3, 4 )); // 输出:24
console.log(proxyMult( 1, 2, 3, 4 )); // 输出:24

 

 
 
 
4、发布-订阅模式(观察者模式)
实现一个发布-订阅模式,分别有三个步骤
  • 首先要指定好谁要充当发布者
  • 给发布者添加一个缓存列表,用来存放回调函数(订阅者订阅时传入的回调函数)
  • 当事件发生的时候,发布者会遍历缓存列表,一次触发里面存放的订阅者回调函数
下面我们来写一个简单的发布-订阅
let event = {
    clientList:[],
    listen:function(key,fn){
        if(!this.clientList[key]){
            this.clientList[key] = []
        }
        this.clientList[key] = fn
    },
    trriger:function(){
        let key = Array.prototype.shift.call(arguments)//获取到特定的key
        let fns = this.clientList[key]
        if(!fns || fns.length == 0){
            //没有指定的回调不作处理
            return
        }
        for(let i = 0; i<fns.length;i++){
            let fn = fns[i]
            fn.apply(this,arguments)
        }
    }
}

 

 
那么要怎么调用呢?首先我们先给需要的对象绑定,也就是实例化
 
//给一个新的对象,绑定所有event的属性(深拷贝)
let installEvent = function(obj){
    for(let i in event){
        obj[i] = event[i]
    }
}
let sales = {}
installEvent(sales)
sales.listen('mike',function(price){//第一个顾客订阅牛奶的价格
    console.log('牛奶的价格是:',price)
})
sales.listen('apple',function(price){//第二个顾客订阅苹果的价格
    console.log('苹果的价格是',price)
})


//超市发布牛奶的价格
sales.trriger('mike',5)
//超市发布苹果的价格
sales.trriger('apple',10)

 

接下来我们又想到了一个需求,假如顾客只想知道一次商品的价格,此外超市再发布同样商品的价格的时候我不想知道了,那要怎么办呢?我们是不是可以想到取消订阅?为了增加取消订阅的功能,我们在原有的基础上新加一个remove功能,用来移除对应的事件
let event = {
    clientList:[],
    listen:function(key,fn){
        if(!this.clientList[key]){
            this.clientList[key] = []
        }
        this.clientList[key].push(fn)
    },
    trriger:function(){
        let key = Array.prototype.shift.call(arguments)//获取到特定的key
        let fns = this.clientList[key]
        if(!fns || fns.length == 0){
            //没有指定的回调不作处理
            return
        }
        for(let i = 0; i<fns.length;i++){
            let fn = fns[i]
            fn.apply(this,arguments)
        }
    },
    remove:function(key,fn){
        let fns = this.clientList[key]
        if(!fns || fns.length == 0){
            //没有人订阅
            return
        }
        if(!fn){
            //有人订阅但是没传入具体的函数,就是该事件所有的回调都要删除
            fns = []
            fns.length = 0
        }else{
            //只取消订阅事件的某个函数,也就是只有一个顾客取消订阅该事件
            for(let i = 0;i<fns.length;i++){
                let _fn = fns[i]
                console.log('jjj',_fn,fn)
                if(_fn === fn){
                    fns.splice(i,1)
                    console.log('移除之后的fns',fns)
                }
            }
        }
    }
}


//给一个新的对象,绑定所有event的属性
let installEvent = function(obj){
    for(let i in event){
        obj[i] = event[i]
    }
}
let sales = {}
installEvent(sales)
let milefn1 = function(price){//第一个顾客订阅牛奶的价格
    console.log('第一个顾客订阅牛奶的价格是:',price)
}


sales.listen('mike',milefn1)


sales.listen('apple',function(price){//第二个顾客订阅苹果的价格
    console.log('第二个顾客订阅苹果的价格是',price)
})
sales.listen('mike',function(price){//第三个顾客订阅牛奶的价格
    console.log('第三个顾客订阅牛奶的价格是:',price)
})
//超市发布牛奶的价格
sales.trriger('mike',5)
//超市发布苹果的价格
sales.trriger('apple',10)
//顾客a取消订阅发布牛奶的时间
sales.remove('mike',milefn1)
sales.trriger('mike',5)

 

到了这里,想必你也会有一个疑虑,上面实现的发布-订阅模式还是有点问题。比如,我在a超市想要订阅牛奶的价格,但是有想要订阅b超市牛奶的价格,这个时候又得重新实现一次b超市的发布订阅模式。有没有可能做一个全局的,超市想要发布的时候直接去这个全局变量发布,订阅的时候也在这个全局变量中进行订阅。
 
其实这个非常简单,我们把Event当做一个全局变量就可以了,不去实例化
let Event = {
    clientList:[],
    listen:function(key,fn){
        if(!this.clientList[key]){
            this.clientList[key] = []
        }
        this.clientList[key].push(fn)
    },
    trriger:function(){
        let key = Array.prototype.shift.call(arguments)//获取到特定的key
        let fns = this.clientList[key]
        if(!fns || fns.length == 0){
            //没有指定的回调不作处理
            return
        }
        for(let i = 0; i<fns.length;i++){
            let fn = fns[i]
            fn.apply(this,arguments)
        }
    },
    remove:function(key,fn){
        let fns = this.clientList[key]
        if(!fns || fns.length == 0){
            //没有人订阅
            return
        }
        if(!fn){
            //有人订阅但是没传入具体的函数,就是该事件所有的回调都要删除
            fns = []
            fns.length = 0
        }else{
            //只取消订阅事件的某个函数,也就是只有一个顾客取消订阅该事件
            for(let i = 0;i<fns.length;i++){
                let _fn = fns[i]
                console.log('jjj',_fn,fn)
                if(_fn === fn){
                    fns.splice(i,1)
                    console.log('移除之后的fns',fns)
                }
            }
        }
    }
}


Event.listen('mike',function(price){//第一个顾客订阅牛奶的价格
    console.log('订阅牛奶的价格',price)
})
Event.trriger('mike',5)//第一个超市发布牛奶的价格
Event.trriger('mike',21)//第二个超市发布牛奶的价格

 

 
5、命令模式
什么是命令模式呢?命令是什么?主要应用在什么场景呢?
命令模式中的命令指的是一个执行某些特定时期的指令,最常见的应用场景是:有时候要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接受者能够消除彼此之间的耦合关系。
 
//给按钮安装命令,就是给具体的对象去添加具体的命令
let setCommand = function(button,command){
    button.onclick = function(){
        command.execute()
    }
}
//按钮的定义,对象具体的功能
let MenuBar = {
    refresh:function(){
        console.log('刷新页面')
    }
}
//封装命令类
let RefreshMenuBarCommand = function(receiver){
    return {
        execute:function(){
            receiver.refresh()
        }
    }
}
//使用
let refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
setCommand(document.getElementById('button1'),refreshMenuBarCommand)

 

现在已经实现了基本的命令模式,如果我们添加一个撤销功能呢?就像我们下棋一样,一个棋子就是一条命令,把它放在棋盘上的一个位子就是执行它,悔棋就是撤销这条命令。下面一起来看一下怎么实现撤销。
每下一步棋之前,都记录下来这个棋子在棋盘上的位子,撤销的时候就把棋子移动到这个已经记录下来的原来位置。
//定义一个棋子
let MoveCommand = function(reciever,pos){
    this.reciever = reciever
    this.pos = pos
    this.oldPos = null
}
//定义下棋的操作
MoveCommand.prototype.execute = function(){
    this.reciever.start(this.pos)//移动到当前位置
    this.oldPos =  this.receiver.dom.getBoundingClientRect()[ this.receiver.propertyName ]//记录移动前的位置
}
//定义撤销的操作
MoveCommand.prototype.unexecute = function(){
    this.reciever.start(this.oldPos)
}

 

 
6、组合模式
所谓组合模式,就是很多小的命令组成一个宏命令,宏命令之间又可以组成一个宏命令,如此往复,组成一颗茁壮的树。等到执行这个组合命令的时候,就遍历这棵树,直至所有的叶节点都被执行完成。
/******************************* Folder ******************************/
var Folder = function( name ){
this.name = name;
this.files = [];
};
Folder.prototype.add = function( file ){
this.files.push( file );
};
Folder.prototype.scan = function(){
console.log( '开始扫描文件夹: ' + this.name );
for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
file.scan();
}
};
/******************************* File ******************************/
var File = function( name ){
this.name = name;
};
File.prototype.add = function(){
throw new Error( '文件下面不能再添加文件' );
};

File.prototype.scan = function(){
console.log( '开始扫描文件: ' + this.name );
};
/**接下来创建一些文件夹和文件对象, 并且让它们组合成一棵树,这棵树就是我们 F 盘里的现有文件目录结构:*/
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 );
/**现在的需求是把移动硬盘里的文件和文件夹都复制到这棵树中,假设我们已经得到了这些文件对象:*/
var folder3 = new Folder( 'Nodejs' );
var file4 = new File( '深入浅出 Node.js' );
folder3.add( file4 );
var file5 = new File( 'JavaScript 语言精髓与编程实践' );
/**接下来就是把这些文件都添加到原有的树中:*/
folder.add( folder3 );
folder.add( file5 );
/**通过这个例子,我们再次看到客户是如何同等对待组合对象和叶对象。在添加一批文件的操
作过程中,客户不用分辨它们到底是文件还是文件夹。新增加的文件和文件夹能够很容易地添加
到原来的树结构中,和树里已有的对象一起工作。
我们改变了树的结构,增加了新的数据,却不用修改任何一句原有的代码,这是符合开放-
封闭原则的。
运用了组合模式之后,扫描整个文件夹的操作也是轻而易举的,我们只需要操作树的最顶端
对象:*/
folder.scan();

 

 
需要注意的是,组合模式不是父子关系,组合对象把请求委托给它所包含的所有叶对象,所有的对象都拥有一个统一的接口。此外,对所有的叶对象的操作要有一致性,就是所有叶对象都要执行。还有,一个叶对象只能从属于一个父节点,不能被执行两次。
 
组合模式适用的情况:
  • 表示对象的部分-整体 层次结构。组合模式可以方便地构造一棵树来表示对象的 部分-整体 结构。
  • 希望统一对待树中所有的对象。组合模式可以忽略组合对象和叶对象的区别,在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就不用写一堆的if else来分别处理。
7、模板方法模式
(1)定义:模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常 在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。在模板方法模式中,子类实现中相同的部分被上移到父类中,而将不同部分的留给子类来实现。
 
(2)那什么才算抽象类呢?
类有两种,一种是具体类,一种是抽象类。具体类可以被实例化,抽象类不能被实例化。比如我们去便利店买一瓶饮料,我们和店员说,我要买一瓶饮料,这是不行的,因为要什么饮料呢?饮料只是一个抽象名词,只有当我们真正明确了饮料类型后,才能得到一瓶可乐、咖啡或茶。
那既然不能实例化,抽象类的作用是什么呢?抽象类就是表示一个一致对外的接口。继承了这个抽象类的所有子类,都将拥有跟抽象类一致的接口方法, 抽象类的主要作用就是为它的子类定义这些公共接口
 

class Beverage{

// 抽象饮料类
init(){
this.boilWater()//把水煮沸
this.brew()//冲泡饮料
this.pourInCup()//把饮料倒进被子里
this.addCondiments()//加入调料
}
boilWater(){
console.log('煮沸水')
}
brew(){
throw new Error('子类必须实现brew方法')

}
pourInCup(){
throw new Error('子类必须实现pourInCup方法')

}
addCondiments(){
throw new Error('子类必须实现addCondiments方法')

}
}
class Coffee extends Beverage{

brew(){
console.log('用沸水冲泡咖啡')
}
pourInCup(){
console.log('把咖啡倒进被子里')
}
addCondiments(){
console.log('加糖和牛奶')
}
}
class Tea extends Beverage{

brew(){
console.log('把沸水浸泡茶叶')
}
pourInCup(){
console.log('把茶倒进杯子里')
}
addCondiments(){
console.log('加柠檬')
}
}
class Test{

prepareRecipe(beverage){
beverage.init()
}
main(){
let coffee = new Coffee()

this.prepareRecipe(coffee)
let tea = new Tea()

this.prepareRecipe(tea)
}
}
let test1 = new Test()

test1.main()

(3)模板方法模式的使用场景
模板方法模式经常被架构师用于搭建项目的框架,架构师定好了框架的股价,程序员继承框架的结构以后,负责往里填空。


(4)使用高阶函数实现模板方法
var Beverage = function(params){

var boilWater = function(){

console.log('把水煮沸')
}
var brew = params.brew || function(){

throw new Error('子类必须实现brew')

}
var pourInCup = params.pourInCup || function(){

throw new Error('子类必须实现pourInCup')

}
var addCondiments = params.addCondiments || function(){

throw new Error('子类必须实现addCondiments')

}
var F = function(){}

F.prototype.init = function(){

boilWater()
brew()
pourInCup()
addCondiments()
}
return F

}
var Coffee = Beverage({

brew(){
console.log('用沸水冲泡咖啡')
},
pourInCup(){
console.log('把咖啡倒进杯子里')
},
addCondiments(){
console.log('加糖和牛奶')
}
})
var coffee = new Coffee()

coffee.init()

 

8、享元模式
(1)享元是什么?
享元模式是一种用于性能优化的模式,核心是运用共享技术来有效支持大量细粒度的对象。
享元模式要求将对象的属性划分为内部状态和外部状态(状态指的是属性),享元模式的目标是尽量减少共享对象的数量。至于如何划分内部状态和外部状态,下面的几条经验提供了一些指引:
  • 内部状态存储于对象内部
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会改变
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
这样以来,我们就可以 把所有内部状态相同的对象都指定为同一个共享的对象,而外部状态可以从对象身上剥离出来,并储存在外部。外部状态在必要时,传入共享对象组成一个完整的对象。
 
(2)享元模式的实例
var Upload = function(uploadType){
       this.uploadType = uploadType//内部状态
   }
   Upload.prototype.delFile = function(id){
       uploadManager.setExternalState(id,this)
       if(this.fileSize < 3000){
           return this.dom.parentNode.removeChild(this.dom)
       }
       if(window.confirm('确定要删除该文件吗?'+this.fileName)){
           return this.dom.parentNode.removeChild(this.dom)
       }
   }
   var UploadFactory = (function(){
       var createdFlyWeightObjs = {}
       return {
           create:function(uploadType){
               if(createdFlyWeightObjs[uploadType]){//如果已经创建过这个对象则在缓冲池中直接返回这个对象
                   return createdFlyWeightObjs[uploadType]
               }
               return createdFlyWeightObjs[uploadType] = new Upload(uploadType)
           }
       }
   })()


    var uploadManager = (function(){
       var uploadDatabase = {}//保存所有upload对象的外部状态,以便在程序运行过程中给upload对象共享对象设置外部状态
       return {
           add:function(id,uploadType,fileName,fileSize){
               var flyWeightObj = UploadFactory.create(uploadType)//同一个类型的上传文件,使用的是同一个flyWeightObj
               var dom = document.createElement('div')
               dom.innerHTML = '<span>文件名称:'+ fileName +', 文件大小: '+ fileSize +'</span>' + '<button class="delFile">删除</button>'
               dom.querySelector('.delFile').onclick = function(){
                   console.log(id)
                   flyWeightObj.delFile(id)
               }
               document.body.appendChild(dom)
               uploadDatabase[id] = {
                   fileName:fileName,
                   fileSize:fileSize,
                   dom:dom
               }
               return flyWeightObj
           },
           setExternalState:function(id,flyWeightObj){
               //设置外部对象,因为所有上传的文件时共享着同一个对象,所以在处理每一个对象的时候,需要将外部状态设置为改文件的外部状态。
               var uploadData = uploadDatabase[id]
               for(var i in uploadData){
                   flyWeightObj[i] = uploadData[i]
               }
               console.log(uploadData,flyWeightObj)
           }
       }
    })()
    var id = 0;
    window.startUpload = function( uploadType, files ){
        for ( var i = 0, file; file = files[ i++ ]; ){
            var uploadObj = uploadManager.add( ++id, uploadType, file.fileName, file.fileSize );
        }
        //遍历完所有上传文件后,实际上只创建了一个uploadObj,也就是说,所有同类型的上传文件共享了同一个上传对象
        console.log(uploadObj)
    }

 

(3)享元模式的适用性
享元模式时一种很好的性能优化的方案,它带来的好处很大程度上取决于如何使用以及何时使用。
  • 一个程序中使用了大量相似的对象
  • 由于使用了大量的对象,造成很大的内存开销
  • 对象的大多数状态都可以变为外部状态
  • 抽离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
 
9、职责链模式
(1)职责链是什么?
职责链模式的定义就是, 使所有对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
请求发送者只需要直到链中的第一个节点就可以把请求发出去,从而弱化了发送者和接收者之间的强联系。
 
(2)实例
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元定金预购,得到100元优惠券')
      }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)//把请求往后面传,记得传入的对象要正确
      }
  }
  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)
  chainOrder500.passRequest(2,true,500)
  chainOrder500.passRequest(3,true,500)
  chainOrder500.passRequest(1,false,0)

 

 
 
 
(3)将上面的实例进行修改,使得异步请求也可以使用
var Chain = function(fn){
      this.fn = fn
      this.successor = null
  }
  Chain.prototype.setNextSuccessor = function(successor){
      return this.successor = successor
  }
  Chain.prototype.next = function(){
    return this.successor && this.successor.passRequest.apply(this.successor,arguments)//把请求往后面传,记得传入的对象要正确


  }
  Chain.prototype.passRequest = function(){
      var ret = this.fn.apply(this,arguments)//先调用一下自己,再看下返回结果
      if(ret == 'nextSuccessor'){
          return this.successor && this.successor.passRequest.apply(this.successor,arguments)//把请求往后面传,记得传入的对象要正确
      }
  }
  var fn1 = new Chain(function(){
      console.log(1)
      return 'nextSuccessor'
  })
  var fn2 = new Chain(function(){
      console.log(2)
      var self = this
      setTimeout(function(){
          self.next()
      },1000)
  })
  var fn3 = new Chain(function(){
      console.log(3)
  })
  fn1.setNextSuccessor(fn2).setNextSuccessor(fn3)
  fn1.passRequest()

(4)当第一个函数返回‘nextSuccessor’时,将请求继续传递给下一个函数。定义一个after函数,传入一个方法,执行完当前方法以后,当当前方法返回‘nextSuccessor’时候,就执行传入的函数。
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 = order500yuan.after( order200yuan ).after( orderNormal );

 

 
 
10、中介者模式
(1)中介者的作用是什么?
中介者模式的作用就是解除对象与对象之间的紧耦合关系。所有的相关对象都是通过中介者对象来甜心,而不是相互引用,所以当一个对象发生改变的时候,只需要通知中介者对象就可以了。中介者使各个对象之间耦合松散,而且可以独立地改变它们之间的交互。核心就是将对象和操作的处理分开,交由中介者去进行处理。
(2)用中介者模式来写一个泡泡堂游戏
首先定义Player构造函数和player对象的原型方法,在player对象的这些原型方法中,不再负责具体的执行逻辑,而是 把操作转交给中介者对象
function Player(name,teamColor){
      this.name = name
      this.teamColor = teamColor
      this.state = 'alive'//玩家生存状态
  }
  Player.prototype.win = function(){
      console.log(this.name + 'win!')
  }
  Player.prototype.lose = function(){
      console.log(this.name + 'lost!')
  }
  Player.prototype.die = function(){
      this.state = 'dead'
      playerDirector.reciveMessage('playerDead',this)//给中介者发送消息,宣布当前玩家死亡
  }
  Player.prototype.remove = function(){
      playerDirector.reciveMessage('removePlayer',this)//给中介者发送消息,移除一个玩家
  }
  Player.prototype.changeTeam = function(color){
      //玩家换队
      playerDirector.reciveMessage('changeTeam',this,color)//给中介者发送消息,玩家换队
  }
  var playerFactory = function(name,teamColor){
      //创建玩家的工厂函数
      var newPlayer = new Player(name,teamColor)
      playerDirector.reciveMessage('addPlayer',newPlayer)//给中介者发送消息,新增玩家
      return newPlayer
  }
  var playerDirector = (function(){
      var players = {},operations = {}//players是玩家队伍,operation是中介者可以执行的操作
      //新增一个玩家
      operations.addPlayer = function(player){
          var teamColor = player.teamColor
          players[teamColor] = players[teamColor] || []
          players[teamColor].push(player)//给这个队伍压入一个队员
      }
      operations.removePlayer = function(player){
          var teamColor = player.teamColor,teamPlayers = players[teamColor] || []
          for(var i = teamPlayers.length - 1;i >= 0;i--){
              if(teamPlayers[i] === player){
                  teamPlayers.splice(i,1)//将队员移除队伍
              }
          }
      }
      operations.changeTeam = function(player,newTeamColor){
          operations.removePlayer(player)
          player.teamColor = newTeamColor
          operations.addPlayer(player)
      }
      operations.playerDead = function(player){
          var teamColor = player.teamColor


          var teamPlayers = players[teamColor]
          var all_dead = true
          for(var i = 0,player;player = teamPlayers[i++];){
              if(player.state !== 'dead'){
                  all_dead = false
                  break
              }
          }
          if(all_dead){
              //队员所处的队伍全部死亡
              for(var i = 0,player;player = teamPlayers[i++];){
                  player.lose()//给队伍的所有玩家都宣告lose
              }
              for(var color in players){
                  //遍历队伍
                  if(color !== teamColor){
                      //宣告其他队伍都赢了
                      var teamPlayers = players[color]
                      for(var i = 0,player;player = teamPlayers[i++];){
                          player.win()
                      }
                  }
              }
          }
      }
      var reciveMessage = function(){
          var message = Array.prototype.shift.call(arguments)//获得第一个参数,也就是消息名称
        //   console.log(message,operations,arguments)
          operations[message].apply(this,arguments)
      }
      return {
          reciveMessage:reciveMessage
      }


  })()
// 红队:
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();
// player3.die();
// player4.die();


// player1.remove();
// player2.remove();
// player3.die();
// player4.die();
player1.changeTeam( 'blue' );
player2.die();
player3.die();
player4.die();

 

 
11、装饰者模式
(1)什么是装饰者模式呢?
给对象动态地增加职责的方式成为装饰者模式。 装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
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()

 

 
 
 
 
 
(2)  装饰函数
现在需要一个方法,在不改变函数源码的情况下,给函数增加功能。我们这里实现两个方法,分别是before和after
Function.prototype.after = function(afterFn){

var _self = this

return function(){//返回一个代理函数

var ret = _self.apply(this,arguments)//执行当前函数

afterFn.apply(this,arguments)
return ret



}
}
Function.prototype.before = function(beforeFn){

var _self = this

return function(){

beforeFn.apply(this,arguments)
return _self.apply(this,arguments)//执行原来的函数

}
}

 

 
 
 
上面的before函数单纯的只是负责函数的执行顺序,没有根据函数的返回结果来进行后续的处理。接下来我们优化一下这个方法,使得beforeFn的执行结果返回false的时候,后面的原函数不再执行
Function.prototype.after = function(afterFn){

var _self = this

return function(){//返回一个代理函数

var ret = _self.apply(this,arguments)//执行当前函数

if(ret === false){ return }

afterFn.apply(this,arguments)
return ret



}
}
Function.prototype.before = function(beforeFn){

var _self = this

return function(){

var ret = beforeFn.apply(this,arguments)

if(ret === false){return}

return _self.apply(this,arguments)//执行原来的函数

}
}

 

 
(3)装饰这模式和代理模式的区别
装饰者模式和代理模式看起来非常相像,都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另一个对象的引用,并且都向那个对象发送请求。两者的主要区别就是它们的意图和设计目的,代理模式的目的是,当直接访问本体不方便或者不符合需求的时候,为这个本体提供一个替代者·。装饰者是动态的加入一些行为2.
 
12、状态模式
(1)什么是状态模式
将状态封装成独立的类,并将请求委托给当前的状态对象每当对象的内部状态改变时,会带来不同的行为变化。我们使用的对象,在不同的状态下具有截然不同的行为,这个对象看起来是从不同的类中实例化来的。
 
(2)下面我们来看一个简单的例子。
当电灯开关按下的时候,灯会有三个状态:强光、弱光和关闭。所以每次按下开关的时候,就会在这三个状态之间发生变化。
我们首先封装三个状态:WeakLightState弱光、StrongLightState强光、OffLightState关闭,这三个类内部分别是处于这个状态的时候按钮被下的处理方法。
 
 
var OffLightState = function(light){

this.light = light

}
OffLightState.prototype.buttonWasPressed = function(){

console.log('当前是关闭状态,切换到弱光')
this.light.setState(this.light.weakLightState)//切换到弱光状态
}
var WeakLightState = function(light){

this.light = light

}
WeakLightState.prototype.buttonWasPressed = function(){

console.log('当前是弱光状态,切换到强光')
this.light.setState(this.light.strongLightState)//切换到强光状态
}
var StrongLightState = function(light){

this.light = light

}
StrongLightState.prototype.buttonWasPressed = function(){

console.log('当前是强光状态,切换到关闭')
this.light.setState(this.light.offLightState)//切换到关闭状态
}
var Light = function(){

this.offLightState = new OffLightState(this)

this.weakLightState = new WeakLightState(this)

this.strongLightState = new StrongLightState(this)

this.button = null

}
Light.prototype.init = function (){

var button = document.createElement('button')

var _self = this

this.button = document.body.appendChild(button)

this.button.innerHTML = '开关'

this.currentState = this.offLightState //设置默认状态

this.button.onclick = function(){

_self.currentState.buttonWasPressed()
}
}
Light.prototype.setState = function(newState){

this.currentState = newState

}
var light = new Light()

light.init()

 

 
(3)改写上面的例子,使其更符合JavaScript的特性
var FSM = {

off:{
buttonWasPressed:function(){
console.log('关灯')
this.button.innerHTML = '下一次按我是开灯'

this.currentState = FSM.on

}
},
on:{
buttonWasPressed:function(){
console.log('开灯')
this.button.innerHTML = '下一次按我是关灯'

this.currentState = FSM.off

}
}
}
var Light = function(){

this.currentState = FSM.off

this.button = null

}
Light.prototype.init = function (){

var button = document.createElement('button')

var _self = this

this.button = document.body.appendChild(button)

this.button.innerHTML = '已关'

this.button.onclick = function(){

_self.currentState.buttonWasPressed.call(_self)//把请求委托给FSM状态机


}
}
var light = new Light()

light.init()

 

 
 
 
 
 
 
 
 
 
 
 
 
 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值