面向对象的一点思考

面向对象的一点思考

为避免歧义,本文所指的面向对象,指的不仅是OOP,也包括OOA与OOD。

一、为什么要面向对象

面向对象是为了解决系统的可维护性,可扩展性,可重用性,我们再进一步思考,面向对象为什么能解决系统的可维护性,可扩展性,可重用性? 

面向对象产生的历史原因有下面两点: 

1、 计算机是帮助人们解决问题的,然而计算机终究是个机器,他只会按照人所写的代码,一步一步的执行下去,最终得到了结果,因此无论程序多么的复杂,计算机总是能轻松应付,结构化编程,就是按照计算机的思维写出的代码,但是人看到这么复杂的逻辑,就无法维护和扩展了。

2、 结构化设计是以功能为目标来设计构造应用系统,这种做法导致我们设计程序时,不得不将客体所构成的现实世界映射到由功能模块组成的解空间中,这种转换过程,背离了人们观察和解决问题的基本思路。 

    可见结构化设计在设计系统的时候,无法解决重用、维护、扩展的问题,而且会导致逻辑过于复杂,代码晦涩难懂。于是人们就想,能不能让计算机直接模拟现实的环境,用人类解决问题的思路,习惯,步骤来设计相应的应用程序?这样的程序,人们在读它的时候,会更容易理解,也不需要再把现实世界和程序世界之间来回做转换。 

    与此同时,人们发现,在现实世界中存在的客体是问题域中的主角,所谓客体是指客观存在的对象实体和主观抽象的概念,这种客体具有属性和行为,而客体是稳定的,行为不稳定的,同时客体之间具有各种联系,因此面向客体编程,比面向行为编程,系统会更稳定,在面对频繁的需求更改时,改变的往往是行为,而客体一般不需要改变,所以我们就把行为封装起来,这样改变时候只需要改变行为即可,主架构则保持了稳定。 

于是面向对象就产生了。 

二、究竟什么才是面向对象

首先看看面向对象的三大特征: 

封装:找到变化并且把它封装起来,你就可以在不影响其它部分的情况下修改或扩展被封装的变化部分,这是所有设计模式的基础,就是封装变化,因此封装的作用,就解决了程序的可扩展性。 

继承:子类继承父类,可以继承父类的方法及属性,实现了多态以及代码的重用,因此也解决了系统的重用性和扩展性,但是继承破坏了封装,因为他是对子类开放的,修改父类会导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性,所以继承需要慎用,只有明确的IS-A关系才能使用,同时继承在在程序开发过程中重构得到的,而不是程序设计之初就使用继承,很多面向对象开发者滥用继承,结果造成后期的代码解决不了需求的变化了。因此优先使用组合,而不是继承,是面向对象开发中一个重要的经验。 

多态:接口的多种不同的实现方式即为多态。接口是对行为的抽象,刚才在封装提到,找到变化部分并封装起来,但是封装起来后,怎么适应接下来的变化?这正是接口的作用,接口的主要目的是为不相关的类提供通用的处理服务,我们可以想象一下。比如鸟会飞,但是超人也会飞,通过飞这个接口,我们可以让鸟和超人,都实现这个接口,这就实现了系统的可维护性,可扩展性。 

2.1 使用面向对象的语言写的程序一定是面向对象的吗

那么问题来了,在我们的日常开发中,究竟什么才是面向对象。用SpringMVC开发一个web应用程序算吗?很遗憾,当然不算。尽管Java是一门面向对象的语言,但我并不会认为MVC这种模式是一种面向对象的方法,事实上我认为,MVC是面向对象的。这里要强调一下,我们使用MVC编写的业务代码,往往是反面向对象的,并不是MVC框架本身,MVC框架本身正是一个面向对象的好例子。在使用MVC框架时,我们只需要在Model, View和Controller里面分别实现代码,这正是面向过程编程思想的使用。

当然并不是说面向过程的编程有什么不好,多种编程范式结合灵活使用,才是编程的正确方向。

2.2 使用非面向对象的语言能够写出面向对象的程序吗

答案当然也是可以。面向对象的三大件,封装、继承、多态,没有语言原生的支持就实现不了吗?并不是的。我们以C语言为例,实现封装难吗,不难,但是还挺麻烦的。要自己写一堆get、set方法是吧,当然也没有public、private这种限定词,要自己确保数据的可见性。继承呢?这个相对困难一些,毕竟struct本身是没有继承的,但是通过一些命名技巧,也可以实现。多态呢,多态是最简单的,多态只不过就是if语句的语法糖而已。

其实说这么多,直接看一下Objective-C转成的C语言代码就可以了,因为Objective-C是面向对象的语言,而它又是转成C语言再编译的,所以我们直接看Objc转成的C语言的代码,就是良好的面向对象的C语言的写法。

三、面向对象的问题在哪里

面向对象出现的原因是弥补面向过程编程的不足,以及应对软件危机,然而面向对象的发展却与目的南辕北辙。

3.1 面向对象的意义

举几个例子,python3现在是一门纯粹的面向对象的语言,它里面的任何数据,底层实现都是对象,在一段时期内,python成为了一门pure OOPL一直是python社区的骄傲。然而,这有什么用呢?面向对象是一种编程时使用的思想,与语言的实现无关。以面向对象本身来描述面向对象这种思想的话(有点self-loop的意思),面向对象的编程思想是一种接口,而底层数据是不是对象,在内存里如何存储则是底层实现,像python这样在底层实现上实现纯粹面向对象,而实际用起来写面向对象的程序仍然非常困难的做法,是一种南辕北辙的做法。相比之下,objective-C的底层实现是C语言,是面向过程的,而提供给开发者的是面向对象的语言,这才是面向对象的思想所期望看到的。

3.2 面向对象与高性能是直接相悖的

面向对象要求我们关注接口而不是底层实现,这与高性能编程是直接相悖的。这例子太多了。。。。随便举几个

比如Java的GC,从面向对象的角度来看,垃圾回收是一种接口,而CMS,G1等都是它的底层实现,我们只需要关注接口对不对?我们是这么做的吗,不是,我们对CMS和G1了如指掌,我们还会做一些调优,就是为了我们的代码能最好的利用内存。

比如线程池,线程池就是一个线程的池子对不对,这是它的接口所描述的。而具体的线程池实现,我们不应该关注。实际上呢,线程池如何分配线程,线程池满了之后的策略,我们都一清二楚。为什么?还不是为了实现高性能编程?

接着举,Java的ArrayList的sublist函数,函数的声明可以认为是一种接口,而函数的实现则看做这个接口的实现。sublist就是返回原list的一个子list,是这么简单吗?不是,我们在写代码的时候会关注到,sublist返回的list是原list的部分引用,如果改变了这个子list,那么原list也会改变。我们为什么知道这么详细?因为我们需要优化性能!

例子数不胜数,面向对象要求封装内部实现,而高性能要求我们理解内部实现,甚至自己改变内部实现,他们本身就是完全对立的。而当今计算机性能又远远达不到让我们无须关注它的程度,像BAT这种公司,性能与并发问题多不胜数,面向对象的好处在这里丧失殆尽。

3.3 面向对象里面最蠢的那一个—-java

Java不支持运算符重载,所以无法实现函数对象,不支持多重继承,不提供非面向对象的写法。尽管有了lamda,函数仍然算不上是Java的一等公民,如果你想覆盖一个函数(比如AOP),你所能做的,还是只能继承这个函数所在的对象并覆盖这个函数(cglib就是这么做的)。这样蠢吗,很蠢。我仍然希望可以直接定义一个新的函数赋值上去,哪怕你底层实现是对象,我希望语法上我用起来就像直接覆盖了这个函数一样(正如python的decorator,这个事情也很有趣,我们一般认为这是动态代理,也就是代理模式,而python直接把它叫做decorator,暗示它是装饰器模式)。

这些特性没有的Java只能算是面向对象的一个残次品。但是好歹,还能用。那么接口这个概念的诞生就很奇特了。面向接口的思想有一句话很出名,说什么如果一个动物如果长的像鸭子,走路像鸭子,叫起来像鸭子,那么它就是只鸭子,可以用鸭子接口来持有它。我觉得这个事情太搞笑了。我就问一个问题,接口长的像不像一个没有函数的抽象类,但是接口是一个类吗,不是。接口的存在本身就是自相矛盾的,它是一种对多重继承的权衡,它相比抽象类唯一的好处就是它可以用特殊的方法实现多重继承,而却有好多夸夸奇谈的人认为接口比抽象类好,实在是难以理解。

诞生于Java只上的设计模式。其实设计模式是普遍面向对象的,但是往往在Java中提的多,貌似最初设计模式的诞生也是因为Java。以《设计模式之禅》为例,这本书里面有大量的样例来举例说明设计模式的好处,但是在我眼里这些代码全都是过度设计的典范,几乎没有一个例子是值得参考的。抽象的级别可大可小,如果有需求,我们就细分抽象,如果没有需求,我们就把小的抽象合并成一个大的抽象。所谓的单一职责原则,根本没有办法说清楚什么是单一职责,因为我们要根据需求才能知道抽象的大小维度。

Java本身的实现。面向对象的编程思想要求我们,如果一个接口里面有不需要实现的方法,就新建一个不包含这个方法的接口。比如有个接口List,可以增删改查。如果我们有个ImutableArrayList,提供只读的List,那么按照面向对象的思想,我们应该声明一个ImutableList接口这个类,不提供add和remove,然后让ImutableArrayList实现它,这样才满足里氏替换原则。Java自己的实现有没有遵循这个原则呢?没有,UnsupportedException在jdk的源代码里面有很多,基本都是在一个子类没有实现接口的某个方法抛出的。

四、总结

面向对象远远不是一个足够完美的编程范式,只有与元编程、面向语言的编程等编程范式结合,才能发挥出最大的作用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注