# 常用范式
# 泛型范式
GP,Generic Programming)—— 抽象你的算法
- 基本思想
将算法和其作用的数据结构分离,并将后者尽可能泛化,最大限度实现算法重用。
这种泛化是基于模板的参数多态(parameteric polymorphism),
相比OOp基于继承(inheritance)的子类型多态(subtyping polymorphism),不仅普适性更强,而且效率也更高。
- 特点
泛型编程是算法导向的,即以算法为起点和中心点的,逐渐将其所涉及的概念内涵模糊化、外延扩大化,
将其所涉及的运算抽象化、一般化,从而扩展算法的适用范围。
(算法串联数据,如脊贯肉;数据实化算法,如肉附脊。)
- 最著名的代表 —— STL(Standard Template Library)
起始于C++语言其后亦为Java,C#等吸纳。
- 三个要素
算法(algorithm)
算法是一系列切实有效的步骤
容器(container)
容器是数据的集合,可理解为抽象的数组
迭代器(iterator)
迭代器是算法和容器之间的接口,可以理解为抽象的指针或游标
- 通过模板
泛化了容器——可以是数组、列表、集合、映射、队列、栈、字符串等等
泛化了元素——可以是任何数据类型
泛化了处理方法和限定条件——可以是任何函数,还可以是函子(functor)——自带状态的函数对象
泛化了迭代器——可以从前往后移动,也可以从后往前移动,可以来回移动,可以随机移动,可以按任意预先定义的规律移动。
- 优势
泛型使代码简洁,摆脱底层编码的细节,在更高、更抽象的层次上进行编程设计。
泛型编程不光能泛化概念,还能泛化行为。
# 超级范式
—— 提升语言的级别
- 特点
元编程作为超级范式的一个体现是,它能提升语言的级别。
如果说OOP的关键在于构造对象的概念,那么LOP的关键在于构造语言的语法。
元程序将程序作为数据来对待,能自我发现、自我赋权和自我升级,
有着其他程序所不具备的自觉性、自适应性和智能性,可以说是一种最高级的程序。
LOP的关键在于构造语言的语法
在传统的编程中,运算是动态的,但程序本身是静态的。
在元编程中,二者都是动态的。
元编程将程序作为数据来对待,能自我发现、自我赋权和自我升级,
有着其他程序所不具备的自觉性、自适应性和智能性,可以说是一种最高级的编程。
- 元编程
模板元编程(Template Metaprogramming),与泛型编程密切相关但是自成一派,
隶属于另一种编程范式——元编程(MP,Metaprogramming)。
元编程作为超级范式的一个体现是,它能体现语言的级别。
比如,有了编译器的存在,汇编语言升级为第3代高级语言;
同样借助Yacc、ANTLR之类的元编程工具,第3代语言升级为第4代DSL语言。
将这一模式发挥至极致,便是更加激进的语言导向式编程(LOP,Language-Oriented Programming)。
这种编程范式的思路是:在建立一套DSL体系之后,直接用它们来编写软件,尽量不用通用语言。
- 区别
问题 ——>解决办法 ——>通用语言源程序 ——>可执行程序
通用语言编程 形成概念模型 将解法映射到通用语言 编译器编译
专用语言编程 形成概念模型 将解法映射到专用语言 元编程 编译器
由于DSL比通用语言更简单、更抽象、更专业、更接近自然语言和声明式语言,开发效率显著提高。
- 应用
有时程序的结构需要动态改变,而C++、Java、C#等静态语言是不允许动态变更类的成员或实现代码的,
利用元编程可以突破这种限制。
自动生成源代码的编程也属于另一种编程范式——生产式编程(Generative Programming)的范畴。
区别在于后者更看重代码的生成,而元编程看重的是代码的可执行性。
另外,除了编译期间生成源代码的静态元编程,还有能再运行期间修改程序的动态元编程。
从低级的汇编到一些高级的动态语言,如perl、python、ruby等均支持该功能。
比如许多脚本语言都提供eval函数。
# 切面范式
A(Aspect)OP —— 多角度看问题
- 理论
- 定义
宏观上
AOP是SoC和DIY原则的一种应用
微观上
AOP虽自OOP土壤中长出,却自成一体,并且嫁接到非OOP领地,不仅在纯过程式编程、函数式编程,
甚至逻辑式语言中得到发展,而且本身也具备一定的声明式语言的特征,成为一种新的软件模块化方法
- 问题
不良代码通常有两种病症
结构混乱,或聚至纠缠打结、或散至七零八落
代码重复,叠床架屋、臃肿不堪
解决这些问题的一个有效方法是 抽象与分解
从问题中抽象出一些关注点,再以此为基础进行分解
分解后的子问题主题鲜明且独立完备,即不会牵一发动全身,也不会四分五裂、支离破碎
同时具有相同特征的部分可以像代数中的公因子一样提取出来,提高了重用性,减少了重复性
- 特点
AOP将程序抽象为分解为切面
AOP以切面为模块,它描述的是横切关注点(cross-cutting concerns),
即与程序的纵向主流执行方向横向正交的关注焦点。
不妨回顾下,无论是过程式的函数,还是对象式的方法,都包含了完成的执行代码。
但有些代码横跨了多个模块,以片段的形式散落在各处,
虽具有相似的逻辑,却无法用传统的方法提炼成模块,难以实现SoC和DRY。
典型的例子如:
在调用某些对象的方法,读写某些对象的域、抛出某些异常等前后需要用到统一的业务逻辑,
诸如日志输出、代码跟踪、性能监控、异常处理、安全检查、事务管理,等等。
为了解决此类问题,AOP应运而生。
它将每类横切关注封装到单独的Aspect 模块中,将程序中的一些执行点和相应的代码绑定起来。
单个的执行点称为接入点(join point)
调用某个对象的方法前后
符合预先指定条件的接入点集合称为切入点(pointcut)
所有一set为命名开头的方法;每段绑定的代码称为一个建议(advice)
接入处是点,切入处是面,面由点组成,advice定义于切入点上,执行于接入点处。
- 原理
如果一个程序是一个管道系统,AOP就是在管道上钻一些孔,在每个孔中注入新的代码流。
因此AOP的关键是将advice的代码嵌入到主题程序中,术语称为编织(weaving)
静态编织,通过修改源码和字节码在编译期、后编译期和加载期嵌入代码——请注意,
这里涉及刚才提到的元编程和产生式编程;
动态编织,通过代理等技术在运行期实现嵌入。
- 举例
举例,白光经过三棱镜的折射分解为七色光,是谓光的色散。再经过导致的三棱镜,七色光又重新汇聚为白色。
如果把一个复杂的系统比作复合色的白光,经过第一个三棱镜——关注分离器,系统被分解为不同的切面,
如果不同的单色的采光。这些切面经过第二个三棱镜——编织器,再度合成为原系统
- AOP 与 OOP
OOP只能沿着继承树的纵向方向重用,而AOP弥补了OOP的不足,可以再横向方向重用
OP是OOP的一种补充,尽管AOP不局限于OOP
- 应用
具体的工具包括一些扩展性语言如 AspectJ、AspectC++、Aspect#等和一些框架如 AspectWerkz、Spring、Jboss AOP等。
与OOP一样,AOP在带来便利的同时,也增加了一定的复杂度和性能损耗。
它适用于大中型程序,用在小型程序中则不啻牛刀杀鸡。
- 步骤
分解切面 —— 第一步在设计者的头脑中进行
实现切面 —— 第二步需要程序员编码实现,即分别实现各个切面的advice,并指明advice挂靠的切入点
合成切面 —— 第三步是通过AOP的工具实现的
# 事件驱动
—— 有事叫我叫你,没事别烦我
- 理论
- 事件
在软件中,它一般表现为一个程序的某些信息状态上的变化
- 分类
基于事件驱动的系统一般提供两类内建事件(built-in event)
底层事件(low-level event) 和 原生事件(native event),
在用户图形界面(GUI)系统中,这列事件直接由鼠标、键盘等硬件设备触发;
语义事件(semantic event),一般代表用户的行为逻辑,是若干底层时间的组合。
比如鼠标拖放(drag-and-drop)多表示移动被拖放的对象,由鼠标按下、鼠标移动和鼠标释放三个底层事件组成。
用户自定义事件(user-defined event)
他们可以是在原有的内建事件基础上进行的包装,也可以是纯粹的虚拟事件(virtual event)。
除此之外,编程者不但能定义事件还能产生事件。虽然大部分事件是由外界激发的自然事件(natural event),
但有时程序员需要主动激发一些事件,如果模拟用户鼠标点击或键盘输入等,这类事件被称为合成事件(synthetic event)。
- 事件 & 消息
消息是Windows内部醉基本的通信方式,事件需要消息来传递,是消息的主要来源
每当用户触发一个事件,如移动鼠标或敲击键盘,系统都会将其转化为消息并放入相应程序的消息队列(messge queue)中。
在消息循环中,程序通过GetMessage不断地从消息队列中获取消息,
经过TranslateMessage预处理后再通过DispatchMessage将消息送交窗口过程WndProc处理。
- 流程驱动式编程 & 事件驱动式编程
(Flow-Driven Programming)采用主动轮询(polling),行为取决于自身的观察判断,是流程驱动的 —— 公共汽车 预定路线
(Event-Driven Programming,简称EDP)采用被动等通知,行为取决于外来的突发事件,是事件驱动的 —— 出租车 用户触发
- 概念
- “回调”
强调的是行为方式——低层反调高层,而“抽象接口”强调的是实现方式——正式由于接口具有抽象性,低层才能在调用它时无须虑及高层的具体实现
同步回调和异步回调都使调用者不再依赖被调用者,将二者从代码上解耦,异步调用更将二者从时间上解耦
在软件模块分层中,低层模块为为高层模块提供服务,并且不能依赖高层模块,以保证其可重用性;
通常被调用者(Callee)为调用者(Caller)提供服务,调用者依赖被调者。两相结合,决定了底层模块多为被调用者,高层模块多为调用者。
但低层模块为了追求更强的普适性和可扩展性,有事也有调用高层模块的需求,于是便邀callback前来相助。
控制反转、依赖反转和依赖注射的主题是控制和依赖,目的是解耦,方法是反转,而实现这一切的关键是抽象接口
回调函数的提法较为古老,多出现于过程式编程,抽象接口是更现代,更OO的说法。
从字面上看,‘回调’强调的是行为方式——低层反调高层,而‘抽象接口’强调实现的方式——正式由于接口的抽象性,
低层才能在调用它时,无需虑及高层的具体细节,从而实现控制反转。
- 特征
被动性 - 来自于控制反转
控制反转的主要作用是降低模块之间的依赖性,从而降低模块的耦合度和复杂度,
提高软件的可重用性、柔韧性和可扩展性,但对可伸缩性并无太大帮助
一般通过Callback实现,其目的是降低模块之间的依赖,从而降低模块的的耦合度和复杂度
异步性 - 来自于会话切换
由事件的不可预测性和随机性决定的
独立是异步的前提,耗时是异步的理由
异步过程在主程序中已非堵塞的机制运行,即主程序不必等待该过程的返回就能继续下一步。
异步机制能减少随机因素造成的资源浪费,提高系统的性能和可伸缩性。
- 实现
步骤
实现事件处理器
注册事件处理器
实现事件循环
事件机制设计需考虑的问题
事件定义、事件触发、事件侦查、事件转化、事件合并、事件调度、事件传播、事件处理、事件连带(event cascade)等。
一个典型的事件驱动模块
事件处理器事先在关注的事件源上注册,后者不定期地发表事件对象,
经过事件管理器的转化(translate)、合并(coalesce)、排队(enquence)、分派(dispatch)等集中处理后,
事件处理器接收到事件并对其进行相应的处理。
- 应用
如果一个应用中,存在一些该类特质的因素,比如频繁的出现堵塞呼叫(blocking call),不妨考虑将其包装为事件
发行/订阅模式(publish-subscribe pattern)正是 观察者模式(observer pattern)的别名,
一方面可看作简化或退化的事件驱动式,另一方面可看作事件驱动式的核心思想。
该模式省略了事件管理器部分,由事件源直接调用事件处理器的接口。
MVC架构是观察者模式在架构设计上的一个应用