函数式实现(FP)与面向对象实现(OOP)并无绝对的优劣之分。FP 固然简洁、清晰、自证性强,但学习曲线陡峭也是不争的事实。
上一节讲到断开外部依赖的一种方案——参数注入法。它通过重构原函数,使其接收一个新参数值(即人为控制的星期索引值)。但这里的参数除了基本类型外,还可以将具体星期值的计算逻辑封装到一个函数内,然后将该函数以参数的形式注入原函数。
于是有了函数式注入的第一套方案——函数作参数注入。对原函数模块 password-verifier-time00.js
作如下更改(L2、L3):
const SUNDAY = 0, SATURDAY = 6;
const verifyPassword3 = (input, rules, getDayFn) => {
const dayOfWeek = getDayFn();
if ([SATURDAY, SUNDAY].includes(dayOfWeek)) {
throw Error("It's the weekend!");
}
// more code goes here...
// return list of errors found..
return [];
};
于是单元测试 password-verifier-time00.spec.js
相应变为(L4、L5,以及 L9、L11):
const SUNDAY = 0, SATURDAY = 6, MONDAY = 2;
describe('verifier3 - dummy function', () => {
test('on weekends, throws exceptions', () => {
const alwaysSunday = () => SUNDAY;
expect(() => verifyPassword3('anything', [], alwaysSunday))
.toThrowError("It's the weekend!");
});
test('on week days, works fine', () => {
const alwaysMonday = () => MONDAY;
const result = verifyPassword3('anything', [], alwaysMonday);
expect(result.length).toBe(0);
});
});
实测结果:
【图 5 改为函数作参数后的实测结果】
再进一步,可将传入的函数改造为一个高阶函数(high order function,简称 HOF),让依赖注入逻辑与密码校验逻辑分开。这样就有了书中所说的 工厂函数(factory functions) 方案。此时原函数已经被完全改造了。
password-verifier-time00.js
:
const SUNDAY = 0, SATURDAY = 6;
const makeVerifier = (rules, dayOfWeekFn) => {
return function (input) {
if ([SATURDAY, SUNDAY].includes(dayOfWeekFn())) {
throw new Error("It's the weekend!");
}
const errors = [];
// more code goes here..
return errors;
};
};
module.exports = {
makeVerifier
};
于是单元测试 password-verifier-time00.spec.js
也要同步更新:
const { makeVerifier } = require('../password-verifier-time00');
const SUNDAY = 0, MONDAY = 1;
describe('verifier3 - dummy function', () => {
test('factory method: on weekends, throws exceptions', () => {
const alwaysSunday = () => SUNDAY;
const verifyPassword = makeVerifier([], alwaysSunday);
expect(() => verifyPassword('anything'))
.toThrow("It's the weekend!");
});
test('on week days, works fine', () => {
const alwaysMonday = () => MONDAY;
const verifyPassword = makeVerifier([], alwaysMonday);
const result = verifyPassword('anything');
expect(result.length).toBe(0);
});
});
实测结果同上面的 图 5。这样做的好处,就是让校验的配置独立于校验的执行,在减少原函数参数个数的同时,测试用例的可读性也更强。一举多得。