多余的概念,没必要刻意追求,也追求不来。
举个栗子。上图是随便搜来的Android系统架构图——我也懒得求证它对不对了,总之就这意思——它看起来就是一些积木一样截然分割的“子系统”。
如果你写过程序,那么就会知道,每个子系统都会对外提供一组界面(或者说一组接口、API),其它应用就依赖于这组界面——说成人话就是需要通过API使用这些方块提供的功能。
当然,这些子系统本身也还可以进一步细化、进一步分成一堆“方块”;这些方块也有各自的“界面(interface)”。
很容易想到,如果我们需要为某个“方块”添加一些功能,那么只需要添加一个特殊的“抽象层”即可。
比如,直接输出一串字符到屏幕是调用libc提供的printf/cout;那么“同时还记入日志”该怎么做呢?
办法有很多很多种。
一是增加一个可见的抽象层。比如logged_printf:
int logged_printf(arg...) { log(arg...); return printf(arg...); }
调用printf之前先输出日志,然后把参数转给系统的printf。将来需要记日志程序里就调用loged_printf,否则还调用printf。很简单,对吧?
另一个办法是干脆替换整个libc。
比如,你可以另外编译一个libc.loged.so;当用户需要日志时,它动态链接到新的libc.loged.so;而如果它不需要日志,依旧链接到旧的libc.so即可。
这个“改变链接目标”的动作,在Linux下可以简化成“用link命令改变libc.so这个软/硬链接的目标”,从而做到“随时切换”。
第三个办法是利用函数重载或者默认参数。
比如,printf(args..., log=false),用户把log设为true就记日志,不传入log参数就走旧的printf流程——当然,这个得C++之类支持重载的语言。
此外,面向对象语言还有装饰器模式、策略模式等惯用法;甚至在python等语言中还提供了专门的特殊语法直接支持装饰器:
@log_decorator #调用fun前先记录一下 fun() @login #调用fun前确保用户已经登录;login内部判断用户已登录则不做任何操作 fun() @login #调用fun前确保用户已经登录 @auth_check("admin") #并且只有用户具有admin权限时才允许执行fun @log_decorator #顺便记上一笔 fun() fun() #直接调用fun
总之,你首先得有个较为固定的、被大量重用的界面;然后,在设计层面,你要用策略模式装饰器模式中间层等等手法支持“切换某个功能的开关状态”(或者增加/取消/改变权限检查之类动作);而在应用层面,你无需理解原理,通过某个入口参数、另外一套封装过的API和/或语言直接支持的特殊语法(如python的@)控制某个功能的开关……这一堆东西就被笼统的称为“面向切面编程”。
但是,请注意,“面向切面编程”和“面向对象编程”和“面向过程编程”使劲扯淡都扯不上关系。它不是架构设计者视角也不是开发应用的视角,而是“对(根本没必要了解代码设计的)外行讲解代码”的视角。
说的更清楚一些:面向切面编程不是技术,那是画到ppt里面忽悠客户忽悠老板的、用来营造“高大上”感觉的多余概念;作为架构师、作为开发工程师,你真正应该学的是接口设计(如里氏代换原则、依赖倒置原则等)和设计模式(什么是策略模式?什么是装饰器?不同语言是如何实现这些模式的?有没有语法糖支持,等等);你要解决的是“我该如何支持动态打开/关闭某个功能?应该用装饰器方案还是策略还是配置文件?”问题——当你面对类似问题时,相关设计方案就是自然而然的;但如果没有相关需求,你也没必要矫揉造作的非要搞出这个“味道”,更没必要用AOP忽悠你自己。