你使用的是数据结构还是对象?
你编写的是数据结构还是对象
写 Java 的人都会自豪的说我是面向对象编程(或许没有那么自豪)。但我们在实际大型网络项目中进行开发的时候,我们最经常编写的各种对象,他们设计的是否真的妥当。
如果你学过 C 语言的话应该知道,C 语言中允许用户自己指定多个数据并用struct
关键字将数据组合为一个整体,而称这种数据结构为结构体。,例如定义一个学生信息结构体:
我们不难发现因为 C 语言是面向过程的,结构体中只能有各种数值,并没有可以执行的函数。那么,如果我们换成 java 来创建一个学生对象呢?看起来可能是这样的:
如果我们用默认的无参构造方法去尝试实例化一个 Student 类,那么显然我们会得到一个 student 对象。那么问题就出现了,我们在 Java 中创建的这个 student 对象其本质上和 C 语言中的 student 结构体似乎没有区别,甚至在访问其中的成员时都是直接使用的student.num
。
而在实际工作中我们一般不直接将 Student 类中的属性设置为公共类型(public),而是为其添加了赋值器与取值器(get()和 set()方法),例如:
或者我们使用 Lombok 来简化编写:
但上面两种的写法本质上是一样的。他们的改进是将将属性设置为了私有的(private),进而让其他人不能直接依赖这些变量,而是通过赋值器与取值器来进行。一般来说这样做有两个好处:
有利于代码的可维护性。可以方便地在对属性访问前后添加必要的操作。
可以在对类进行继承时通过方法覆盖而隐藏属性。
尽管理想如此,但我们会发现很多时候都是在无继承的情况下,使用了不加额外操作的赋值器与取值器。而这样的赋值器与取值器由于会直接将私有变量暴露出去,所以其本质上与 public 的属性功能是一样的(此处不讨论框架依赖等情况),进而就会发现它与 C 语言的结构体也是一致的。也就是说:
如果类中只有私有属性与无额外功能的赋值器与取值器,那么我们认为这样的类本质上只是数据结构,而非对象。
对象的意义
上文说到,如果直接将对象中的私有属性暴露出来外部的话,那它本质上是一个数据结构,而非对象。那我们在设计对象的时候应该考虑什么呢?
屏蔽细节。
我认为,对于对象的设计,核心的理念就是屏蔽对象内部的细节,使用户无需了解数据的具体实现就能操作数据本体。而这些操作的“方法“便是由数据提供的。举个例子:
我们可以看出,PointA 类提供了一个数据结构用于描述一个点。而 PointB 接口则是提供了两组方法,使得用户可以按照极坐标系或者笛卡尔坐标系来设置数据内容。
对于 PointA 来说,不论你实际在处理坐标系的时候是哪种坐标系,你同样可以直接操作其中的属性来执行逻辑。但由于暴露了实现,所以在使用 PointA 的时候,我们必须要了解多个概念,例如:
PointA 中的所有属性:x、y
x 与 y 必须同时存在。
我们在使用 PointA 的时候,因为使用了其中暴露出来的实现,所以就只有了解了实现的内容后才能进行类的使用。当前只有两个属性,而如果是一个拥有大量属性的类,则这种情况会导致业务开发效率降低很多。
而再看 PointB,其本身已经是抽象出来的接口了,接口本身也没有额外的属性。尽管没有属性,但是一组去值器也准确无误地呈现了一种数据结构。但是你不用关心其底层是用一个 x,y 实现的,还是其他方法实现的。而进一步的,PointB 还通过方法完整的描述了数据的存取逻辑:需要原子性的设置两个数据,但是取值的时候可以分别进行获取(PointA 也是这个逻辑,但是在类中无法体现出来)。
同样的,我们再看一下下面的例子:
两个例子看起来 counterA 中我们可以获取更全的数据,但是如果我们只关心进度的话,那么 countB 中的方法则是正好满足我们需要的。实际上 counterA 中的数据,我们看起来是取值器,而即便我们拿出来了,数据是否正常也是我们需要关心的,而 countB 的话则只提供了一个抽象的方法,而我们并不需要知道方法的实现细节。所以:
对于对象设计来说,并不只直接根据模型进行字段设计再添加上赋值器、取值器就可以了的。仍旧要需要提供最合适的、最少细节的方法给用户。
应该用哪个呢
尽管上文似乎是说:数据结构不太好。但事实上,我们可以换一个角度来描述:
数据结构将数据细节暴露出来了,没有提供方法。
对象提供了首相方法,但是数据在抽象备好。
简单来说就是,数据结构没方法,对象无法拿到细节。那么根据两种数据类型的设计方式就可以进行确定。
面向过程
由于数据结构没有方法,则我们在使用数据结构的时候,不同的数据结构都是直接从中取值,然后再进行业务流程的处理。但相对来说,由于数据结构中没有方法,当希望添加一种新的处理方式的时候,那我们就可以在进行“面向过程编程”的部分添加一个新的方法,而这方法的添加则不需要对已有的数据结构进行调整。但是相反的,这种方法处理的时候不便于添加新的数据结构,因为需要修改所有的方法。
面向对象
而如果是面向对象编程,则我们可以通过类的继承关系,在核心方法中使用多态来实现功能。而一般来说,如果要新增一个子类,则我们就新的实现接口就可以。但是如果我们是要新增接口方法,则要将所有的子类都进行调整。
从上述分析来看,面向对象与面向过程两种方式互为补充。在任何一个复杂的心痛中,我们都不可能只增加子实现类或者只新增方法能力。所以我们就要评估自己项目的变动方向:
如果需求导致方法调整比较多,则可以尝试面向过程编程。
如果需求导致新增子类比较过,则可以用面向堆成编程。
或者说,面向对象不容易处理的其实面向过程就能落地,反之亦然。
最后
作为 Java 的开发人员都会说自己是面向对象编程,但事实上很多场景下的对象本质上是数据结构。对象可以在功能调用的时候屏蔽细节,但在实际的开发中,使用数据结构的方式也是有实际的优势应用场景的。
版权声明: 本文为 InfoQ 作者【蜜糖的代码注释】的原创文章。
原文链接:【http://xie.infoq.cn/article/c2d5a3a50e5642470eb7a5d38】。文章转载请联系作者。
评论