面向对象的再次理解

2018-12-13  本文已影响44人  00d1ed2b53ae

从大学的第一门编程课老黄就教我们面向对象,但是这么多年过去了,代码写了不少,最近在写PHP和Python的时候对编译型语言和解释型语言的区别有点感悟,于是联想到面向对象语言,才发现自己写了这么多年的面向对象,总结起来却不甚完善,于是重新整理了一下面向对象编程以及面向对象的数据库设计的相关知识

1.什么是对象?

面向对象乃是语言的核心,是程序设计的思想。语言的面向对象技术包括了面向对象和面向过程的基本概念,面向对象的特征,语言的类,对象,修饰符,抽象类等一系列的知识点

首先让我们来了解一下什么是对象?

没错!要了解面向对象我们肯定需要先知道对象到底是什么玩意儿。关于对象的理解很简单,在我们的身边,每一种事物的存在都是一种对象。总结为一句话也就是:对象就是事物存在的实体。下面举个简单的例子,比如人类就是一个对象,然而对象是有属性和方法的,那么身高,体重,年龄,姓名,性别这些是每个人都有的特征可以概括为属性,当然了我们还会思考,学习,这些行为相当于对象的方法。不过,不同的对象有不同的行为

1.1面向对象的特征

1.1.1封装:

就是把属性私有化,提供公共方法访问私有对象。举个简单的例子,我们去Apple店里买个iPhoneX,我们不需要知道它是怎么制造的,我们只需要知道它能用来打电话,上网和用来装B就行了。对于程序设计,用户只需要知道类中某个方法实现了什么样的功能,需要该功能的时候直接去调用就行了,不用去关心方法内部的实现细节

1.1.2继承:

当多个类具有相同的特征(属性)和行为(方法)时,可以将相同的部分抽取出来放到一个类中作为父类,其它类继承这个父类。继承后子 类自动拥有了父类的属性和方法,比如猫,狗,熊猫他们共同的特征都是动物,有颜色,会跑,会叫等特征。我们可以把这些特征抽象成我一个Animal类(也就是父类)。然而他们也有自己独特的特性,比如猫会抓老鼠,喵喵叫,熊猫有黑眼圈,能吃竹子,狗会汪汪。于是我们就根据这些独特的特征分别抽象出来Cat,Dog,Panda类等。他们拥有Animal类的一般属性和方法,也拥有自己特有的某些属性和方法。
但特别注意的是,父类的私有属性(private)和构造方法不能被继承。另外子类可以写自己特有的属性和方法,目 的 是实现功能的扩展,子类也可以复写父类的方法,即方法的重写。子类不能继承父类中访问权限private的成员变量和方法

1.1.3多态:

简单来说就是“一种定义,多种实现”。同一类事物表现出多种形态。Java语言中有方法重载和对象多态两种形式的多态

1.1.4抽象:

抽象是从许多事物中,舍弃个表的,非本质的属性,抽取出共同的,本质的属性的过程。例如教师,学生和工人,他们共同的特质是人类,既然是人类就有共同的属性:性别,年龄,身高,体重等。抽象的过程就是比较的过程,通过比较找出事物之间的共同属性,通过比较区分本质。

2.面向对象与面向过程的对比:

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。

举一个例子:

用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。

蛋炒饭制作的细节,我不太清楚,因为我没当过厨师,也不会做饭,但最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。

蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。

到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。

盖浇饭的好处就是”菜”“饭”分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是”可维护性“比较好,”饭” 和”菜”的耦合度比较低。蛋炒饭将”蛋”“饭”搅和在一起,想换”蛋”“饭”中任何一种都很困难,耦合度很高,以至于”可维护性”比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。

总结两者区别:
两句话概括他们的区别,面向对象就是高度实物抽象化、面向过程就是自顶向下的编程

3.面向对象数据库设计

要采用面向对象方法,首先要忘记数据库的存在,采用对象分析方法,先把对象分析和定义出来,保证业务执行逻辑能够被这些对象很好的完成。达到这一点后,再来考虑对象持久化的问题。一句数据库的三大范式以及性能要求来把对象持久化。注意!!这是我们设计数据库要解决的问题是“对象数据高效持久化”,而不是业务逻辑!它不是从需求中推导出来的。例如面向过程的设计中,一张申请表很可能被设计成一张物理表;而面向对象设计中,和可能没有申请表这么一张物理表,而只有“用户资料”、“申请流程”、“申请资质”等对象表,所谓的申请对象,是在运行期由这些对象聚合而成的。

每个对象都有自己的属性和状态,我们需要把这个对象的属性和状态保存在数据库中,那么最理想最最简单的情况,就是一个对象对应一张物理表,而对象之间的关联关系(一对一,一对多,多对多)也可以简答的映射成数据库的主-外键关系。但还有很多非数据库关系需要考虑,如:继承、聚合、依赖等。一张表如何继承自另一张表呢?关系数据库显然没有这样的定义,这就需要用OR-mapping来完成这种语义的转换。例如,当实例化一个子对象时,OR-mapping负责从代表了“父”对象的表中对出父对象属性并将其赋值给子对象,并且当父对象变化时,OR-mapping需要把这一变化反映到所有子对象实例(这只是一种OR-mapping方案,也有在所有子表里荣誉存贮父对象属性来实现的)。再比如聚合对象,一个公司对象由公司基本信息以及一个部门List构成,那么在持久化这个对象时显然需要把它分成公司表和部门表(一对多关系),在业务逻辑执行过程中操作公司对象时它们始终是一体的对象,但当CRUD这个对象时OR-mapping要负责将对对象的操作转化为对两张表的操作。而依赖表示了两个对象之间相互依存的关系,当一个变化时另一个相应的要变化。这在数据库中可以由Insert/update/delete引发的trigger来实现,但更好的做法显然是由OR-mapping来实现这种关系管理。

实际上我们所遇到的情况只会更加复杂,一个复杂的业务对象可能对应的数据库中的许多张表;一些简单的对象也可能只对应数据库中某张表的一部分。现在我们应该明白OR-mapping的作用了,他不是负责将数据表直接翻译成为对象那么简单,它负责的是将对象关系语义转化成数据关系语义。换言之,OR-mapping负责的是“数据”和“表现”的分离,数据如何存贮和查询是一回事(由三大范式和性能优化考虑决定),数据如何表现又是另一回事(由业务执行逻辑和高效面向对象设计决定)。如果一个or-mapping做的足够好,能完美支持对象关系和数据关系的转换的话,你就可以独立的更改对象和数据库,之后只需要重新配置一下mapping关系即可。

一个典型的例子,在面向对象的设计中,业务逻辑和控制逻辑通常是分离的。比如一个订单对象,在业务执行逻辑上,除了业务数据,它还需要一些状态属性来标识流程控制进程;但是流程控制进行通常都不是业务数据的一部分,他只是系统的控制逻辑。在好的面向对象设计中,这种控制逻辑是可以分离出来用另一组对象来标识,再通过对象的聚合或者对象之间的依赖注入来将两者动态编订的。在以数据流为基础的数据库设计中,通常的做法是将状态控制字段与业务字段设计在同一张表里的。其结果是控制逻辑与业务逻辑被静态绑定,这意味着两者都不能独立变化。只要查看一下现在的很多系统中,当流程变化时导致要更改业务表,或当业务数据变化要改流程,就说明该设计不是一个面向对象的设计,或者至少是一个糟糕的面向对象设计。真正好的面向对象设计会分离业务逻辑和控制逻辑,在运行过程中业务对象与流程控制对象是独立加载并在流程控制框架下动态绑定的。这意味着两者都获得了独立变更的能力。在此基础下持久化业务对象和流程控制对象的结果是必然会形成一组流程控制表和一组业务数据表,它们两者之间是没有静态依赖关系的,某个流程实例的控制状态只会存在于流程控制表而不会存在于业务表中。因此,流程控制与业务得以解耦而独立变更。

如果将革命进行得更彻底一些,我们甚至可以仅仅将数据库视为保存数据的一种手段,而放弃数据库的约束,如主-外键约束关系。在以前进行的一个项目中进行了这样的尝试,所有数据库表之间均没有主-外键关系,没有trigger,没有约束,每张表都是独立的,每张表都是直接对象的持久化的结果,数据库甚至不管理对象之间的关联关系,每张表仅由一个唯一的主键ID来标识对象实例。而对象之间的关系全部抽象出来用一组对象关系表来管理,一条关系表记录表示两个对象ID之间的一种关系,由一个对象关系管理框架来管理它们。对象关系管理框架管理对象之间的“关联”、“继承”、“依赖”等简单关系,同事经过扩展,这些关系可以扩展成为更复杂的对象关系,例如可以在关系当中加入时间因素,表示某两个对象在一定时间之内是“内联”的或“继承”的;也加入版本因素,表示某两个对象产生“关联”关系。在这个管理框架下,对象理论上拥有无限的扩展能力,而这种能力却不依赖于数据库。一张数据库表的变化仅仅影响它对应的持久化的那个对象而已。我们完全可以在程序中动态的创造出对象关联(从关系管理框架中加入一个关系实例)从而动态的创造出一个全新的对象,我么也可以扩展关系管理框架中的关系而得到更加复杂的对象组合。

但是彻底的革命也不是完美的,这种与数据库关系彻底的决裂意味着我们同事放弃了数据库的高效,完全由程序来管理对象关系不但引入了一个复杂的框架,同时整体性能也大受影响!例如,一个拥有子对象的对象在采用数据库关系管理时,我们可以用一条SQL语句来加载这个对象;在采用对象关系管理框架以后,我们必须先得到一个对象,然后向关系管理框架咨询它所关联的对象的ID,然后再加载它,这个过程必然产生多条SQL调用。CRUD所有操作都需要额外的向关系管理框架咨询和操作,得到扩展的同时牺牲了性能。但现实就是这样,人生不如意十之八九,得到一些总是会失去一些的。好在在性能要求不太高的场合,这个框架是相当有效的!以致于在项目过程中我们从未对数据库修改头疼过,因为我们的程序逻辑、现实逻辑等与数据库是无耦合的,我们使用的是一种称为ValueObject的POJO来作为业务实体对象和显示对象;而这个ValueObject是由关系管理框架根据对象关系将持久对象(Persistence Object)动态组合出来的。等效于我们解耦了实体对象和实体对象的持久化结果,自然的,数据库的修改就变得轻松很多了。

今天的文章里详细讨论了面向对象方法里数据库的设计方法。如果你是一个面向对象的革命者或愤青,那么你可以宣称面向对象不需要数据库设计(估计这是少数派)!如果你是一个面向过程的保守派,那么你可以宣称数据库设计是一切的核心(估计这是多数派)!然而我们还是现实一些,站在实用主义的角度,承认:

1、面向对象方法是非常行之有效的;数据库设计应当围绕着对象的高效持久化进行而不是以数据库设计为核心;

2、关系数据库的高效及方便不是对象数据库模式在短期内可以轻易达到的,我们不能因为倒脏水把婴儿也泼掉了。

3、最好的方法是根据实际项目对性能和扩展性的要求,在性能要求高的场合可以适当牺牲面向对象的特性来达到性能要求,在扩展性要求高的场合则可以适当牺牲数据库性能来满足扩展性。

上一篇 下一篇

猜你喜欢

热点阅读