原文地址:blog.bitsrc.io/aspect-orie…
译者:Gavin,未经授权禁止转载。
前言
写JS的同学应该都听说过面向对象程序设计(OOP)与函数式编程(FP),对Java或nestjs有一些了解的同学应该也听说过面向切面的程序设计(AOP),但你知道通过原生JS怎么去实现一个最简的AOP功能吗?
幸运的是,就像JS中的OOP与FP一样,你可以毫不费力的将AOP与FP或OOP混用。
AOP简介
AOP为我们提供了一种不需要修改现有逻辑将代码注入到现有函数或对象中的方法。
AOP切面实现的3要素
- Aspect:切面,用于封装要添加的行为
- Advice:通知(增强),它指定了希望执行代码的常见时刻,如:
before
、after
、around
、when throwing
等 - Pointcut:切入点,用于指明在具体需要进行方法增强的位置,比如:
某个特殊的方法
、某个对象下的所有方法
等
基本实现
下面示例用于说明实现AOP的容易程度及其给代码带来的好处。
// 源文件地址:https://gist.github.com/deleteman/1b73da25feabf32db33c611674eb1ca6#file-aop-js
// aop.js
/** 用于获取对象上所有的方法 */
const getMethods = (obj) => Object.getOwnPropertyNames(Object.getPrototypeOf(obj)).filter(item => typeof obj[item] === 'function')
/** 用于在特殊时刻利用自定义函数替换原始方法 */
function replaceMethod(target, methodName, aspect, advice) {
const originalCode = target[methodName]
target[methodName] = (...args) => {
if(["before", "around"].includes(advice)) {
aspect.apply(target, args)
}
const returnedValue = originalCode.apply(target, args)
if(["after", "around"].includes(advice)) {
aspect.apply(target, args)
}
if("afterReturning" == advice) {
return aspect.apply(target, [returnedValue])
} else {
return returnedValue
}
}
}
module.exports = {
// 用于在需要的时刻和位置将注入切面功能
inject: function(target, aspect, advice, pointcut, method = null) {
if(pointcut == "method") {
if(method != null) {
replaceMethod(target, method, aspect, advice)
} else {
throw new Error("Tryin to add an aspect to a method, but no method specified")
}
}
if(pointcut == "methods") {
const methods = getMethods(target)
methods.forEach( m => {
replaceMethod(target, m, aspect, advice)
})
}
}
}
// 源文件地址:https://gist.github.com/deleteman/1efd939193400a569308945eb445e3cd#file-using-aop-js
// using-aop.js
const AOP = require("./aop.js")
class MyBussinessLogic {
add(a, b) {
console.log("Calling add")
return a + b
}
concat(a, b) {
console.log("Calling concat")
return a + b
}
power(a, b) {
console.log("Calling power")
return a ** b
}
}
const o = new MyBussinessLogic()
function loggingAspect(...args) {
console.log("== Calling the logger function ==")
console.log("Arguments received: " + args)
}
function printTypeOfReturnedValueAspect(value) {
console.log("Returned type: " + typeof value)
}
AOP.inject(o, loggingAspect, "before", "methods")
AOP.inject(o, printTypeOfReturnedValueAspect, "afterReturning", "methods")
o.add(2,2)
o.concat("hello", "goodbye")
o.power(2, 3)
上面代码很简单,一个基本对象有3个方法。其中包含两个注入切面,一个用于记录接受到的属性,另一个用于分析其返回值并记录其类型。最终输出:
AOP的好处
- 封装横切关注点:封装横切关注点有利于阅读和维护整个项目
- 灵活的逻辑:当涉及到切面注入时,围绕通知(增强)和切入点实现的逻辑可以提供很大的灵活性
- 跨项目重用:可以将切面视为组件,即可以在任何地方运行小而分离的代码片段,可以轻松在不同项目中共享使用
AOP的主要问题
- 隐藏逻辑性与复杂性:用函数式编程的思维讲AOP具有副作用,它可以向现有的方法中添加不相关的行为,甚至可以替换原有方法的整个逻辑
总结
AOP提供了做任何想做事情的能力,如果缺乏良好的编程实践,可以导致非常大的代码混乱。简单总结就是权力越大,责任越大。如果想正确地使用AOP,那么首先必须得理解其核心思想和最佳实践。
AOP是OOP的完美补充,由于JS的动态特性,我们可以非常容易的实现它。同时它也提供了强大的功能,模块化和解耦大量逻辑的能力,甚至可以轻松实现跨项目的逻辑共享。
但如果不正确的使用,可能也会造成大量的代码混乱。
相关链接(译者注)
- nestjs中参考AOP技术的实现
- 在eggjs中实践依赖注入与面向切面编程
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!