没网站怎么做二维码扫描连接,济南做网站推广哪家好,海口网站建设优化公司,响应式 购物网站模板文章目录 3.4 函数式依赖注入技术 Functional injection techniques3.5 模块化依赖注入技术 Modular injection techniques 写在前面 上一篇的最后部分对第三章后续内容做了一个概括性的梳理#xff0c;并给出了断开依赖项的最简单的实现方案#xff0c;函数参数值注入法。本… 文章目录 3.4 函数式依赖注入技术 Functional injection techniques3.5 模块化依赖注入技术 Modular injection techniques 写在前面 上一篇的最后部分对第三章后续内容做了一个概括性的梳理并给出了断开依赖项的最简单的实现方案函数参数值注入法。本篇接着介绍函数式注入与模块化注入的具体实现。窃以为后者是本章的难点需要用心体会作者的设计思路。 接 上篇 3.3 小节
3.4 函数式依赖注入技术 Functional injection techniques
函数式实现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(Its 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(Its 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(Its 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(Its 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。这样做的好处就是让校验的配置独立于校验的执行在减少原函数参数个数的同时测试用例的可读性也更强。一举多得。
3.5 模块化依赖注入技术 Modular injection techniques
这一节开始加大难度了主要目的在于让大家感受一下模块化注入的繁琐。为什么会这么繁琐呢因为以模块的方式注入依赖项虽然写起来很爽但对于单元测试而言完全是另一码事。回到最开始的原函数版本——
password-verifier-time00.js
const moment require(moment);
const SUNDAY 0, SATURDAY 6;const verifyPassword (input, rules) {const dayOfWeek moment().day();if ([SATURDAY, SUNDAY].includes(dayOfWeek)) {throw Error(Its the weekend!);}// more code goes here...// return list of errors found..return [];
};module.exports {verifyPassword,
};怎样从单元测试的角度断开上述代码中的直接依赖呢答案是没有现成的方法只能“曲线救国”。这就要用到 3.2 节补充的 Seam 缝隙的概念了通过构造一个特定的写法以便将直接依赖项替换成单元测试能够直接干预的代码实现 控制反转。
以下代码给出了一个示例版本
核心重构1根据模块化注入方案重构的新版待测函数示例
const originalDependencies {moment: require(moment)
};let dependencies { ...originalDependencies };const inject (fakes) {Object.assign(dependencies, fakes);return function reset () {dependencies { ...originalDependencies };};
};const SUNDAY 0; const SATURDAY 6;const verifyPassword (input, rules) {const dayOfWeek dependencies.moment().day();if ([SATURDAY, SUNDAY].includes(dayOfWeek)) {throw Error(Its the weekend!);}// more code goes here...// return list of errors found..return [];
};module.exports {SATURDAY,verifyPassword,inject
};相比其他小节上述代码是本章最难的一段。原因很简单——我之前从没这样认真研究过开个玩笑 。
先来仔细看看这段代码。为了成功断开由 moment.js 引入的直接依赖需要构造一个新的写法即第 17 行中的星期值生成逻辑
// 改造前
const dayOfWeek moment().day();
// 改造后
const dayOfWeek dependencies.moment().day();先甭管 dependencies 怎么定义的写成第 4 行的形式后最初的 moment().day() 就变成了 dependencies 下的 moment() 方法这时只要再设计一个注入逻辑比如第 7 至 12 行的 inject(fakes) 函数并让它在运行测试时对 dependencies.moment 属性 重新赋值这样就实现了原始依赖 moment 模块的平替从而实现 控制反转最后为了不破坏原函数逻辑等到单元测试结束还得再设计一套 重置逻辑让 dependencies.moment 重新指向 moment 模块。这就是模块化注入的大致流程。
与之配套的单元测试代码如下
核心代码2模块化改造后的新校验函数在单元测试模块中的应用示例
const { inject, verifyPassword, SATURDAY } require(../password-verifier-time00);const injectDate (newDay) {const reset inject({moment: function () {// were faking the moment.js modules API here.return {day: () newDay};}});return reset;
};describe(verifyPassword, () {describe(when its the weekend, () {it(throws an error, () {const reset injectDate(SATURDAY);expect(() verifyPassword(any input)).toThrowError(Its the weekend!);reset();});});
});第一次看这两段代码头是真的晕。比如上面的高阶函数 injectDate()它接收一个普通的星期值 newDay然后用这个值构造了一个测试专用的伪对象 fakes示例中没有单独声明并直接传入 inject() 方法最后将执行结果——即包含重置逻辑的 reset() 函数——作为函数结果返回。最后在测试用例的第 18 行和第 23 行实现了控制的反转与依赖项的重置。
抱着将信将疑的心理我在本地实测了上述代码居然真的可以这样写 【图 6 按照模块化注入方案重构原函数得到的实测结果】
正当我惊叹于作者对 JavaScript 闭包的深入理解时大佬又再次复盘上述写法对比了该方案的优劣
优势解决了最开始的直接依赖问题使用时也相对比较简单按大佬的说法多写几遍自然就有感觉了……劣势即闭包 dependencies 中的 moment 属性与依赖的 moment 模块之间未能实现解耦。遇到真实项目测试就傻眼了成千上万个依赖项接口难不成还得挨个重构成特定的闭包属性
为此作者给出了如下建议
永远不要在代码中直接使用第三方依赖最好加一个适配层缓冲一下这样就不怕第三方库修改接口或者更换其他依赖项了。慎用这个天坑的模块注入方案换成其他实现方案比如之前介绍的视函数为参数、或者函数柯里化或者后面紧接着会介绍的 构造函数 以及 接口 的解决方案。
总之这一节主要是给后续的高级方案做铺垫用的对我而言也是增长见识的一节让我知道设计模式中的适配器模式在单元测试中原来还能这么用。