【编程·落地与升华·框架篇】实战·手写ORM框架(一)

一、什么是O/RM(Object Relation Mapping 对象关系映射)?

  Relation关系:本质上来源于数学,是多个集合进行笛卡尔积后的某个子集(取决于某种关系)。

  Object对象:来源于面向对象开发思想,是面向对象开发的基本粒子。

       在使用面向对象方式进行程序设计与实现时,若数据持久化方案采用关系型数据库实现时,后端与数据库的通讯过程必然导致本质上的思维冲突(面向对象与关系模型)、技术冲突。为解决这个问题,原始的做法是通过宿主程序(如C#、Java等面向对象语言)中的数据库访问工具(如ADO.NET)撰写嵌入式SQL,并建立与数据库关系模式相应的实体类型,通过手工书写特定通讯过程进行数据的传递。

  显而易见,这种方式缺乏上层抽象,并没有从根本上去解决,是一种临时性做法,其中两端通讯主体受业务影响而千变万化,其中的映射随之而变,庞大的变化必然导致软件开发难度增加、维护成本增加,那么有没有什么方式能够去解决这个问题?

  答:O/RM,即对象关系映射,是一个通过面向对象的编程方式去构建的高可复用的通讯框架技术。采用一致的编程思想(面向对象)来进行后端代码与数据库通讯的表达。甚至可以使开发者完全不了解数据库语言就能进行安全有效地进行程序开发。

  如此一看,ORM好神奇,但是他也有所弊病,就像中国的成语翻译成英文,无论你怎么翻译,他都不能原汁原味的用极简的描述表达深刻的内含,这是两种文化、思想上的差异,就像O/R一样,始终是有一部分做不到的。所以对于软件开发工程师,数据库技术依然是一个重点。另外,为达到高可复用的较高层抽象通常是会带有运行时性能损失,就这一点来说,ORM的是不如传统方式的。

  O/RM并不是什么新鲜的技术,成熟的解决方案有很多,如Java环境的Mybatis、Hibernate等,Net环境的EF框架等。那么我们为什么还要尝试手写呢?其实这个问题很简单,对个人来说,是个锻炼技术的好方式,对企业来说,不是每一款ORM都能够适配局下的技术框架,有的过于简单,有的却显得笨重。况且ORM就算已经成熟,仍有一些做不到的东西,我们为什么不去尝试一下呢?

  

二、深刻理解O/RM过程,追寻技术落地的根源

  传统方式案例:首先建立实体Student类,实例化该类Liubei(刘备)对象用于接收数据,然后通过嵌入式SQL语句检索进行PKey检索,将数据传递至后台解析后写入Liubei对象的属性中。

  示例代码:

 

  这是一段表达了基本逻辑却并不可靠的代码,可靠性暂且不谈,就基本逻辑而言,该方法实现了从Student表中根据Pkey值检索并读取一条元组数据到Student类对象的过程,无论是张三还是李四,我们都可以通过调用这个方法动态的实现从关系到对象的转化。

  但是,这段代码仅仅实现了在Student类内的代码复用,如果再来一个“教师类 / 教师表”‘、“课程类 / 课程表”的映射我们如何实现呢?分析发现,逻辑与上述方法基本相同,难道要复制粘贴上述代码更改方法名与返回值类型吗?稍后回答。

  “当我们希望事情保持原状,就到了必须发生改变的时候了”--朱塞佩·托西马·笛·兰佩杜萨,《豹》

 

  软件的复杂性来源于”变化”,对此,我们可以通过面向对象三个基本特征中的“封装思想”进行分析。

  例如,生活中的快递服务,无论你邮寄或接收的是键盘鼠标也好、化妆品也好,快递员在电话里总是会对您说:“先生(or 小姐),您的快递(or 包裹)到了”!有此可见,对于快递员来说,他不关心你究竟里面装的是什么,业务角度讲他也不应该关心,他所应该关心的是关于包裹的信息,如寄件人,寄件地点,接受人,接收地点,接收人联系方式等。如此一来,快递员会忽略那些无关的变化(究竟装的是什么),而更加专注于自己的业务。

  那么什么是封装?如果你觉得仅仅是用一个包裹把东西包起来,那就说明要走的路还很远。就快递案例分析,包裹概念的提出不是没有根源的,基于生活经验,我们可以将“邮寄的键盘”、“邮寄的鼠标”、“邮寄的香水”归纳为“邮寄的东西”,我们称之为包裹,这是一种抽象层次的自然上升,就像“身高”概念的提出,归纳了170.180.190cm等无穷无尽的可能,这样我们人脑能够接受并处理的信息才不会爆炸,人类语言的构建过程就是这样,不断地归纳信息,通过一种不变去屏蔽变化。我们在进行软件设计时同样会面临这样的问题,不同于生活经验,计算机逻辑或业务逻辑的陌生感让我们不太容易找到那个“不变的东西”,也许正是如此,才更有价值。

  回归主题,面对O/R通讯问题,传统方式会将变化封装在特定的类内,通过代码我们发现,其实可重用的地方还很多,那么我们如何去解决这个问题呢?

  看似是一个代码重用问题,实则是关于设计的问题。通过代码看问题,解决过程会非常精致,但却略显狭隘。缺乏较高层次的归纳。

  我们不妨试着画一张图:

 

  画这张图需要一点基本功,是关于面向对象与数据库系统的基本知识。万丈高楼平地起,基础概念体系的建立程度往往决定了技术发展的未来。

  ·关系型数据库系统基本概念“:

    1.关系:数据库里的一张表,有具体的结构,有数据。比如学生表,其中存有张三李四等人的信息。

    2.关系模式:关系的抽象描述,有具体的结构,没有数据。比如一张学生表或一张教师表。

    3.关系模型:关系模式的抽象,用于描述什么是关系模式,说明了关系模式“要有”表名,列名,域,约束等。

  ·面向对象的基本概念“:

    1.对象:面向对象程序中的基本操作单位,包含具体的结构与值。例如一个具体的一个学生张三。

    2.类:对象的抽象,有具体的结构,但没有数据,例如学生类、教师类等。

    3.类的元描述:类的抽象,描述了什么是类,例如类要有名称,要有属性,要有数据类型,要有方法等。

  基于上述概念分析,真正进行数据传递的是对象与关系,如张三与关系中的元组(数据库的一条记录)张三,或一个集合对象对应一个关系。为寻求一致的方案,解决这个实际的通讯问题,我们可以进行两步抽象:

  第一步,将【张三对象/张三元组】的【数据传递】归纳为【学生类/学生表】的【具体映射】,其实这就是我们的传统做法,实现了无论是张三还是李四,只要他是学生,就可以通讯。

  第二步,为达到更高层次的复用需求,我们再次向上归纳,我们发现,无论是学生表还是老师表,最终都是表,无论是学生类还是教师类,都是类,由此,我们找到了问题合适的终点(不要过度设计)。其实我们所做的映射,本质上就是在做(【类名/关系名】,【类属性/关系列名】,【数据类型/域】,【逻辑约束/关系约束】)的映射。

  回到出发点,我们本质上是想在“后端”程序中实现这个转化过程,而不是在数据库端完成,所以我们应去寻找关于“类”向上抽象的解决方案,实际上就是在表达类的描述。

  借助面向对象工具(本文采用.NET平台,C#编程语言),比如可以使用抽象基类(将所有要进行通讯的实体类都继承这个类)、泛型类(通过制定运行时泛型指定来描述实体类),甚至我们可以使用一个属性的集合来表示(对象与数据库的通讯,实际本质上就是类的属性在进行通讯,这种只含有Get Set方法的模型被称为贫血模型,所以一定程度上,我们可以用一个属性集合来表征与数据库通讯过程中特定的类的描述)。

  具备通讯实体的定义能力之后,我们需要去表达映射,就单表单对象通讯来说,对象属性和表的列名不一定一致,传递过程要建立一种特定的映射,而这种特定的映射也是可以进行抽象表达的,比如使用XML配置文件存储、静态或非静态类存储、或者利用特定语言的语法,比如C#中的特性来进行描述。

映射描述 \ 类元描述 抽象基类 泛型类 属性组合
配置文件 1 2 3
类文件 4 5 6
特殊语法 7 8 9

  至此,根据类元描述方式与映射描述方式的不同,提出了9种解决方案,那么究竟孰高孰低?异或都有什么各自最适合的应用场景呢?深夜已至,我已无力再战,以后有时间逐一分析,但是各位看客,希望能独立思考,在我接下来的实现篇中逐步体会。

猜你喜欢

转载自www.cnblogs.com/labixiaohei/p/12157838.html