日常开发中会经常不知不觉的使用到各种设计模式,这里以nodejs中异步调用转化为同步调用为例,一起探索下适配器模式。(本文的旨在为把复杂的东西简单化,理清思路换位思考,相信看完本文你应该会掌握处理复杂问题的思路)
如果熟悉nodejs的同学应该会对node各种异步编程库有所涉猎。这里我不讲那些库,只是讲解一个简单的函数实现,以此来切入适配器模式。
此时,我想把nodejs的异步函数,改为promise的方式调用,即aa('xx').then().then();这样。此时就需要一个适配器,来转化下我们的异步函数,如果你的抽象思维比较好,脑子里有一整套模型,那轻易就能写出一个转化函数。但是,很多时候我们没有那么高的抽象能力,怎么办呢?从结果入手。下面是一个实例:
改造前fs.readFile('xxx', function(data){});
期望改造后的结果是aaa('xxx).then(function(data) {});
aaa怎么来的呢?就是通过适配器模式生成的函数:var aaa = cb2Promise(fs.readFile);
从结果入手,那就容易了。首先要创建一个cb2Promise函数,然后他需要一个函数作为参数,返回一个可以调用函数,次函数调用的返回的结果是promise对象。 一步一步来:
function cb2Promise(func) {
return function () {
return new Promise(function(resolve, reject){});
}
}
大体的构架已经出来了。现在就是完善内容。
第二步,我们传入了一个函数,这个函数就是我们实际调用的东西:
function cb2Promise(func) {
return function () {
return new Promise(function(resolve, reject){
func();
});
}
}
现在问题来了func需要两个参数,第一个是url第二个是回调。但是,现在参数从哪里来呢?从结果来 aaa('xxx).then(function(data) {}); 这里我们不是传入了一个'xxx'了么。这个怎么获取呢?此时有两种方式,第一种最简单的,直接以形参的方式传入:
function cb2Promise(func) {
return function (path) {//path从调用时传入
return new Promise(function(resolve, reject){
func(path, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
}
这样是不是很简单的解决了问题。第二种方式是采用arguments来获取,这种方式对扩展性比较友好,(你如你想fs.open('xxx', 'w+', function() {}))这个难道还得再写一个函数么,这不科学。所以用arguments来获取参数扩展性更好。具体怎么做呢?这时,我们想到了call和applay函数,而arguments为类数组对象,很明显此时我们用arguments函数:
function cb2Promise(func) {
return function () {//path从调用时传入
var args = arguments;
return new Promise(function(resolve, reject){
func.apply(args , function(err, data) {//有问题
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
}
额,是不是感觉那里不对了?是的 func.apply(args , function(err, data) {})此时参数有两个,我们实际需要的只是一个数组。所以,我们想着把回调函数放到args中去。此时应该这样做:
function cb2Promise(func) {
return function () {
var arg = arguments ? [].slice.apply(arguments) : [];
return new Promise(function (resolve, reject) {
arg.push(function(err, data) {
if (err){
reject(err);
} else {
resolve(data);
}
})
func.apply(this, arg);
})
}
}
是不是很简单,然后我们再稍微加一点错误处理,让程序更健壮稳定:
function cb2Promise(func) {
if (typeof func !== 'function') throw new Error('第一个参数必须为函数');
return function () {
var arg = arguments ? [].slice.apply(arguments) : [];
return new Promise(function (resolve, reject) {
arg.push(function(err, data) {
if (err){
reject(err);
} else {
resolve(data);
}
})
func.apply(this, arg);
})
}
}
这样一个简单的适配器就完成时,一步步下来是不是很简单。