设计模式——迭代器模式
迭代器模式是一种很常见的模式,大部分语言都有了内置的迭代器实现,JS也不例外,从Array.prototype.forEach到ES6新增的专用迭代器都属于迭代器模式。下面我们来探究一下迭代器模式的封装方法。
迭代器模式封装原则
和其他设计模式一样,迭代器模式也是将变化的部分与不变的部分隔离开来。特点如下:
- 将遍历访问集合结构内容的逻辑封装,不暴露其内部结构。
- 为同一种集合结构提供相同的接口,从而支持内容不同的集合结构的遍历。
内部迭代器
内部迭代器如其名,它已经在内部完全定义好了迭代规则,只留出一个初始调用的接口。优点就是调用很方便,不必关心内部实现。缺点也是由于封装性太强导致拓展性不足。一个简单的内部迭代器实现如下:
let each = function(ary, callback) {
for (let i = 0, l = ary.length; i < 1; i++) {
callback.call(ary[i], i, arr[i]);
};
}
外部迭代器
外部迭代器相比与内部迭代器拓展性更强一些,将请求下一个元素的接口暴露了出来,我们可以手动控制迭代的过程或顺序。实例如下:
let Iterator = function(obj) {
let current = 0;
let next = () => {
current += 1;
};
let isDone = () => current >= obj.length;
let getCurrItem = () => obj[current];
return {
next,
isDone,
getCurrItem
};
};
let example = Iterator([1, 2, 3]);
while (!iterator.isDone()) {
console.log(example.getCurrItem()); // 1, 2, 3
iterator.next();
}
外部迭代器虽然调用方式复杂,但由于更灵活,也能满足更复杂的需求。具体业务中使用哪一种迭代器由业务复杂度决定。当然由于ES6的迭代器模块的出现,使得外部迭代器的创建更加简单。例子如下:
function* iterator(obj) {
for (let current = 0; current < obj.length; current++) {
yield obj[current];
}
}
let example = iterator([1, 2, 3]);
更详细的内容可以去迭代器相关文章查看。
应用场景实例
我们现在要实现一个根据不同浏览器使用不同方式上传文件的需求。首先我们会优先使用控件上传,如果浏览器不存在控件则使用Flash,如果没有Flash则使用表单上传。按照逻辑很容易写出如下代码:
let getUploadObj = () => {
try {
return new ActiveXObject('TXFTNActiveX.FYNUpload'); // IE 控件
} catch(e) {
if (supportFlash()) {
let str = "<object type='application/x-shockwave-flash'></object>";
return $(str).appendTo($('body'));
} else {
let str = '<input name="file" type="file" />'; // 表单
return $(str).appendTo($('body'));
}
}
};
虽然实现了功能,但问题还是一如既往。所有的上传方式代码耦合到了一起,不仅难以阅读,而且在今后需要改进上传上式选择上也要深入内部实现。下面我们将每种方法都封装到各自的函数里,然后通过迭代器模式获取可用的方法 。
let getActiveUploadObj = () => {
try {
return new ActiveXObject('TXFTNActiveX.FTNUpload');
} catch(e) {
return false;
}
};
let getFlashUploadObj = () => {
if (supportFlash()) {
let str = "<object type='application/x-shockwave-flash'></object>";
return $(str).appendTo($('body'));
}
return false;
};
let getFormUploadObj = () => {
let str = '<input name="file" type="file" />'; // 表单
return $(str).appendTo($('body'));
}
现在我们已经将方法函数分别进行了封装,接下来只需要创建一个迭代器对方法函数进行遍历即可。
let iterator = (...arg) => {
for (let i = 0, fn; fn = arg[i++];) {
let uploadObj = fn();
if (uploadObj !== false) {
return uploadObj;
}
}
};
let uploadObj = iterator(getActiveUploadObj, getFlashUploadObj, getFormUploadObj);
重构之后,我们成功分离了方法和迭代逻辑,使得可拓展性更强。以后如果要增加其他上传方式只要新增方法函数再迭代即可。
-- EOF --