最近经常在思考对象、类、接口、AOP之类的问题
想想各种“面向对象”编程语言,类和对象的关系却是截然不同的
在C++里,类只是对程序员和编译器有用的东西,到了运行时,基本只剩下了一个虚函数表,除非显式使用typeid和dynamic_cast,类型检查也都是编译时进行的
而在Java/C#里,类在运行时依然存在,运行时也可以方便的进行类型匹配,还可以通过反射机制,访问那些在编译时无法确定的类的成员
还有便是如 Javascript / Lua 之类的脚本语言,每一个object对象(非int,string之类的原生类型)其实就是一个字典,对象根据名字在字典中查找属性,属性的值可以是原生类型,也可以是object,甚至可以是function。如果属性是function,那么调用这个function时会自带 this/self对象,如同在C++/Java/C#中一样。至于类是什么?那不过是当字典中找不到属性时用来查找默认值的一个普通object罢了
说了这么多,最终想表达的是,“面向对象”面向的是对象,和类没多大关系
类的作用在于定义对象中共通的地方,同时对对象作出约束,如果没有类,确实会有很多麻烦。早期的脚本语言是完全不存在类的概念的,我见过一个Java script 的代码,在构建一个对象的时候除了初始化属性外,还要为对象加一堆function进去,除了多出很多代码外,这些function也很容易被篡改掉
但是,类有一个最大的限制,就是它是静态的,在编译时就决定了。运行时的多态,是通过选择不同的类来创建对象实现的。运行时创建类型即便不是不可能,也是非常困难的——这可是我好不容易用一堆OpCodes拼出一个类,却没有调试工具只能把生成的Assembly输出成dll文件用IL Desassembly看字节码查错后得出的切身体会
当然,OO发展了这么多年,也写出了很多很好的软件,但毕竟还是有人不满于它在设计上的诸多限制,于是发展出了“面向方面的编程(Aspect Oriented Programming)”
相对于OOP,AOP要求更多的运行时特性(顺带一提,我的那个运行时创建的类,正是为了完成AOP中的一个“小”功能):AOP的要点在于设计、编程时只考虑单一方面的问题,在运行时再把这些方面组织起来。举个简单的例子,在设计对象逻辑的时候完全不需要考虑对象存储的问题,后者则由专门的存储方面处理。虽然在OO中,也可以通过种种手段分离这两者,但至少需要在对象中留下存储的接口,如果对象需要在属性改变时立即保存属性值则更是如此。而在AOP中,这些接口完全是不需要的!
在静态的类中,要实现AOP的诸多功能是很困难的,因为AOP的这些功能需要改变对象的行为,而对象的行为是由类(静态的)决定的。
但是,如果彻底摒弃类的概念,而代之以诸多特征(trait)*;对象的行为也不再由类决定,而是由不同的特征拼合而成,就可以作到:
- 根据需要组合不同的特征,得到不同行为的对象
- 运行时增减、替换特征,改变对象的行为
这里所谓的特征与类的最大差别在于,一个对象只能对应一个类,却能对应多个特征,而这些特征之间却没有任何关系——一个特征可以依赖于某个接口,但绝不会依赖于其他的特征,否则上述特性就都不存在了。特征之间也不需要继承关系——一个特征P可以依赖于某个接口I,但不需要了解I是怎么被实现的,只要对象中的某一特征实现了I,P就能正常的被访问。
* 从Scala中借来的名词。当然Scala的trait是编译时的。