在软件开发中,代码的可读性和可维护性是至关重要的。一种常见的优化技巧是使用链式调用来简化复杂的操作,提高代码的清晰度和简洁性。在本文中,我将通过一个实际案例来演示如何使用链式调用来优化代码。
一个例子
我们先通过一个示例直观地感受一下不同的编程方式是如何处理同一个业务逻辑需求的,以便于更深入地了解链式调用带来的便利。
假设后端在响应中返回了一个包含了多个对象的数组类型的数据,它的结构如下,classes属性中记录了每个人最近一周每天上的课程,记录中的每一项既可能是字符串,也可能是字符串组成的数组(代表所上课程不止一种):
ini
const data = [
{ name: 'Alice', classes: ['Math', 'English', ['Physics', 'Chemistry'],'English'] },
{ name: 'Bob', classes: ['Math', ['Biology', 'Chemistry']] }
];
现在为了分析学生们的课程安排,我们需要把在classes属性中出现过的所有课程都记录下来,并按照字母顺序对其排序,相同的课程只需要出现一次即可。下面就来看看不同的开发者可能会如何实现这样的功能。
初级开发者的代码
ini
function getAllCourses(data) {
let resultMap = {};
//遍历每一条记录的classes字段,然后使用对象实现去重功能
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].classes.length; j++) {
let classes = data[i].classes[j];
if (typeof classes === "string" && !resultMap[classes]) {
//处理字符串类型的情况
resultMap[classes] = 1;
} else if (Array.isArray(classes)) {
//处理数组类型的情况
classes.forEach((item) => {
if (!resultMap[item]) {
resultMap[item] = 1;
}
});
}
}
}
//从对象中生成并返回最终结果
return Object.keys(resultMap).sort((a, b) => a.localeCompare(b));
}
const data = [
{ name: "Alice", classes: ["Math", "English", ["Physics", "Chemistry"]] },
{ name: "Bob", classes: ["Math", ["Biology", "Chemistry"]] },
];
const result = getAllCourses(data);
console.log(result); // [ 'Biology', 'Chemistry', 'English', 'Math', 'Physics' ]
上述代码虽然实现了所要求的功能,但但暴露了太多实现细节。有如下几个问题:
- 代码结构较为复杂,嵌套了两层循环,可读性稍显不足,理解起来有一定难度。
- 如果输入的数据格式不符合预期,可能会导致意外错误,缺乏代码的健壮性。
- 代码使用了命令式的编程风格,缺乏灵活性和可扩展性。
- 当需要添加新功能或修改现有功能时,由于代码结构复杂,可能需要对代码进行大量的修改。
中级开发者的代码
javascript
function getAllCourses(data) {
return sortAndUnique(
flatmap(
data.map((item) => item.classes),
[]
)
);
}
//排序去重
function sortAndUnique(arr) {
let resultMap = {};
arr.forEach((i) => (resultMap[i] = 1));
return Object.keys(resultMap).sort((a, b) => a.localeCompare(b));
}
//数组扁平化
function flatmap(arr, result) {
if (Array.isArray(arr)) {
arr.map((item) => {
flatmap(item, result);
});
} else {
result.push(arr);
}
return result;
}
const data = [
{ name: "Alice", classes: ["Math", "English", ["Physics", "Chemistry"]] },
{ name: "Bob", classes: ["Math", ["Biology", "Chemistry"]] },
];
const result = getAllCourses(data);
console.log(result); // [ 'Biology', 'Chemistry', 'English', 'Math', 'Physics' ]
上述代码质量明显比前面的要好。这份代码遵循了"单一职责"的开发原则,将数组的类型判断、数组的扁平化、数组的排序和去重等可重用的方法从业务逻辑中剥离出来,形成了独立的方法。
高级开发者的代码
ini
const getAllCourses = (data) => {
let result = data;
function map(fn) {
result = result.map(fn);
return api;
}
function flattenDeep() {
function flatten(arr) {
return arr.reduce(
(acc, val) =>
Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val),
[]
);
}
result = flatten(result);
return api;
}
function sortBy() {
result = result.sort();
return api;
}
function sortedUniq() {
result = Array.from(new Set(result));
return api;
}
function value() {
return result;
}
const api = {
map,
flattenDeep,
sortBy,
sortedUniq,
value,
};
return api;
};
// 使用示例
const data = [
{ name: "Alice", classes: ["Math", "English", ["Physics", "Chemistry"]] },
{ name: "Bob", classes: ["Math", ["Biology", "Chemistry"]] },
];
const result = getAllCourses(data)
.map((item) => item.classes)
.flattenDeep()
.sortBy()
.sortedUniq()
.value();
console.log(result); // [ 'Biology', 'Chemistry', 'English', 'Math', 'Physics' ]
上述代码
- 代码结构清晰,每个功能分离明确。每个方法只关注自己的功能,这使代码容易理解和维护。
- 链式调用风格使得代码简洁,调用者可以直观地看到数据是如何一步步处理的。
- 如果需要添加更多的处理步骤,只需在 api 对象中添加新的方法即可,方便扩展。
总结
封装链式调用的关键是确保每个方法都返回正确的对象,以支持后续的链式调用,并且要确保链式调用的方法在设计时考虑周全,易于理解和使用。