一个面向对象的程序员怎么能够围绕数据库驱动的编程?
我已经在C#和Java中进行了一年多的编程,对于面向对象的编程有一个很好的把握,但是我的新的项目需要一个数据库驱动的模型。 我正在使用C#和Linq这似乎是一个非常强大的工具,但我在devise一个数据库围绕我的面向对象的方法麻烦。
我的两个主要问题是:
我如何处理我的数据库中的inheritance? 比方说,我正在build立一个员工名册,我有一个抽象类Event。 从事件我派生了抽象类ShiftEvent和StaffEvent。 然后我有具体的类Shift(从ShiftEvent派生)和StaffTimeOff(派生自StaffEvent)。 还有其他派生类,但为了争辩,这些就足够了。
我应该有一个ShiftEvents和StaffEvents单独的表? 也许我应该为每个具体的课程分开表格? 这两种方法似乎都会在与数据库交互时给我带来麻烦。 另一种方法可能是有一个事件表,这个表对于我的任何具体类中的每种types的数据都有可以为空的列。 所有这些方法都觉得他们可能会妨碍可扩展性。 我可能还没有考虑过第三种方法。
我的第二个问题:
如何以面向对象的方式处理集合和一对多关系?
假设我有一个Products类和一个Categories类。 类别的每个实例都将包含一个或多个产品,但产品本身不应包含类别知识。 如果我想在数据库中实现这一点,那么每个产品都需要一个映射到类别表的类别标识。 但是从OO的观点来看,这会引入更多的耦合。 产品甚至不应该知道类别的存在,更不用说包含类别ID的数据字段! 有没有更好的办法?
Linq to SQL使用每个类的表解决scheme:
http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/10/01/linq-to-sql-inheritance.aspx
其他解决scheme(如我最喜欢的LLBLGen)允许其他模型。 就个人而言,我喜欢带有鉴别器列的单表解决scheme,但这可能是因为我们经常查询inheritance层次结构,因此将其视为普通查询,而查询特定types仅需要“在哪里”进行更改。
所有的说法和做法,我个人觉得把OO映射到桌子上是把马车放在前面。 一直有人断言OO和关系之间的阻抗不匹配已经解决了……并且已经有了很多OO特定的数据库。 他们没有一个能够消除这种关系的强大的简单性。
相反,我倾向于用应用程序devise数据库,将这些表映射到实体并从那里构build。 有些人认为这在devise过程中是OO的损失,但是在我看来,数据层不应该在应用程序中足够高的话来影响高阶系统的devise,因为您使用关系模型进行存储 。
我有相反的问题:经过多年的数据库devise后,如何让我的脑海里浮现出来。 十年前,我经历了多年的“结构化”平面文件编程之后,才开始了解SQL的问题。 在类和数据实体分解之间有足够的相似性来误导你,认为它们是等价的。 他们不是。
我倾向于同意这样的观点,即一旦你致力于一个关系数据库的存储,那么你应该devise一个规范化的模型,并在不可避免的情况下危及你的对象模型。 这是因为DBMS比你自己的代码更受限制 – build立一个受损的数据模型更可能会让你痛苦。
这就是说,在给出的例子中,你有select:如果ShiftEvent和StaffEvent在属性方面大部分是相似的,并经常作为事件一起处理,那么我倾向于使用types列实现单个事件表。 单表视图可以是分离出子类的有效方法,而在大多数数据库平台上是可更新的。 如果这些类在属性方面有更多的不同,那么每个表可能更合适。 我不认为我喜欢三桌的想法:在关系devise中“有一个或没有”的关系是很less需要的。 无论如何,你总是可以创build一个Event视图作为两个表的联合。
对于产品和类别,如果一个类别可以有多个产品,但是反之亦然,那么以这种正常的关系方式来表示产品就包含一个类别ID。 是的,这是耦合,但它只是数据耦合,这不是一个真正的罪恶。 该列可能应该编入索引,以便检索某个类别的所有产品是有效的。 如果你真的被这个概念所震惊,那么就假装它是一个多对多的关系,并使用一个单独的ProductCategorisation表。 这并不是什么大不了的事情,虽然这意味着一个潜在的关系并不真正存在,并可能会误导未来的应用程序。
在我看来,这些范式(关系模型和面向对象)适用于不同的领域,使得尝试创build它们之间的映射变得困难(并且毫无意义)。
关系模型是关于表示事实 (如“A是一个人”),即具有“独特”性质的无形事物。 谈论同一事实的几个“实例”是没有意义的 – 事实就是这样。
面向对象编程(Object Oriented Programming)是一种编程模式,详细描述了构build计算机程序以满足某些标准(重用,多态,信息隐藏…)的方式。 一个物体通常是一些有形事物的隐喻 – 汽车,引擎,pipe理者或人等等。有形事物不是事实 – 可能有两个不同的具有相同状态的物体,而不是它们是相同的物体(因此,例如,Java中的equals和==)。
Spring和类似的工具以编程方式提供对关系数据的访问,以便事实可以用程序中的对象来表示。 这并不意味着面向对象和关系模型是相同的,或者应该与彼此混淆。 使用Realational Model来devise数据库(事实集合)和OOP来devise计算机程序。
TL; DR版本(对象关系阻抗不匹配蒸馏):
事实=冰箱里的食谱。 对象=你的冰箱的内容。
框架如
- hibernatehttp://www.hibernate.org/
- JPA http://java.sun.com/developer/technicalArticles/J2EE/jpa/
可以帮助你顺利解决这个inheritance问题。 例如http://www.java-tips.org/java-ee-tips/enterprise-java-beans/inheritance-and-the-java-persistenc.html
在处理面向对象的方法之前,我也了解了数据库devise,SQL,尤其是以数据为中心的世界视图。 对象 – 关系阻抗不匹配仍然让我感到困惑。
我发现最接近的事情是:不是从面向对象的程序deviseangular度,而是从面向对象的分析angular度来看待对象。 我得到的关于OOA的最好的书是Peter Coad在90年代初写的。
在数据库方面,与OOA比较的最佳模型不是数据的关系模型,而是实体 – 关系(ER)模型。 ER模型并不是真正的关系型,它没有指定逻辑devise。 许多关系的护教士认为这是ER的弱点,但实际上是它的强项。 ER最好不用于数据库devise,而用于数据库的需求分析,也称为数据分析。
ER数据分析和OOA令人惊讶地相互兼容。 ER又与关系型数据build模兼容,因此与SQL数据库devise兼容。 OOA当然与OOD兼容,因此也与OOP兼容。
这可能看起来很漫长。 但是,如果你保持足够抽象的东西,你不会浪费太多的时间在分析模型上,你会发现很容易克服阻抗不匹配。
在学习数据库devise方面最重要的事情就是:在你的问题中,像你所反对的主键链接的外键一样的数据连接并不可怕。 它们是将相关数据捆绑在一起的本质。
在预数据库和面向对象的系统中有一种称为涟漪效应的现象。 涟漪效应是大型系统看似微不足道的变化最终导致整个系统随之而来的必要变化。
OOP主要通过封装和信息隐藏来包含涟漪效应。
关系数据build模主要通过物理数据独立性和逻辑数据独立性来克服连锁反应。
从表面上看,这两者似乎是根本上相互矛盾的思维模式。 最后,你会学习如何使用它们两个好的优势。
我的猜测离开了我的头顶:
关于inheritance的话题,我build议有3个表:Event,ShiftEvent和StaffEvent。 事件具有共同的数据元素,就像最初定义的那样。
我想,最后一个可以换个angular度。 你可以有一个带有类别ID和产品ID的表格,没有其他列,对于一个给定的类别ID这个返回的产品,但产品可能不需要得到该类别作为其自身描述的一部分。
一个很大的问题是:你怎么才能把你的头靠近呢? 这只是需要练习。 您尝试实施数据库devise,遇到devise问题,重构并记住下一次什么是有效的,哪些没有。
为了回答你的具体问题……这是一个小小的意见,如“我该怎么做”,没有考虑到业绩需求等。 我总是开始完全正常化,并根据现实世界的testing从那里开始:
Table Event EventID Title StartDateTime EndDateTime Table ShiftEvent ShiftEventID EventID ShiftSpecificProperty1 ... Table Product ProductID Name Table Category CategoryID Name Table CategoryProduct CategoryID ProductID
同样重申了Pierre所说的 – 像Hibernate这样的ORM工具使处理关系结构和OO结构之间的摩擦变得更好。
为了将inheritance树映射到关系模型有几种可能性。 例如,NHibernate支持“每个类的层次结构”,每个子类的表和每个具体类策略的表: http : //www.hibernate.org/hib_docs/nhibernate/html/inheritance.html
对于第二个问题:您可以在您的数据库中创build一个1:n关系,其中Products表中有一个外键给Categories表。 但是,这并不意味着您的产品类需要引用它所属的类别实例。 您可以创build一个Category类,其中包含一组产品或一系列产品,您可以创build一个产品类,该产品类没有所属类别的概念。 再次,你可以使用(N)Hibernate轻松做到这一点; http://www.hibernate.org/hib_docs/reference/en/html/collections.html
听起来像是在发现对象关系阻抗不匹配 。
产品甚至不应该知道类别的存在,更不用说包含类别ID的数据字段!
我不同意这里,我会认为,不要提供一个类别ID,而是让你的ORM为你做。 然后在代码中,你会有类似的东西(从NHib和Castle的ActiveRecord中借用):
class Category [HasMany] IList<Product> Products {get;set;}
…
class Product [BelongsTo] Category ParentCategory {get;set;}
那么如果你想看看你所在的产品是什么types的,只需要做一些简单的事情:
Product.ParentCategory
我想你可以用不同的方式设置orm,但是对于inheritance问题,无论哪种方式,我都会问…你为什么在意? 要么用对象去做,要么忘记数据库,要么以不同的方式去做。 可能看起来很愚蠢,但是除非你真的不能拥有一堆表格,或者出于某种原因不想要一张表格,为什么要关心数据库呢? 例如,我有几个inheritance对象的设置,我只是去做我的事情。 我还没有看到实际的数据库,因为它不涉及我。 底层的SQL是关于我的,正确的数据回来了。
如果你必须关心数据库,那么你将需要修改你的对象,或者想出一个自定义的方式来做事情。
我想在这里有一点实用主义会很好。 对象和表之间的映射在这里和那里总是有点奇怪。 这就是我所做的:
我使用Ibatis与我的数据库(Java到Oracle)交谈。 每当我有一个inheretance结构,我想要一个子类存储在数据库中,我使用“鉴别器”。 这是一个技巧,你有一个表的所有类(types),并有你可能想要存储的所有领域。 表中有一个额外的列,包含一个string,Ibatis使用它来查看它需要返回哪种types的对象。
它在数据库中看起来很有趣,有时候会让你陷入与不属于所有类的领域的关系,但是有80%的时间这是一个很好的解决scheme。
关于类别和产品之间的关系,我会在产品中添加一个categoryId列,因为这会让生活变得非常简单,无论是明智的还是明智的。 如果你真的坚持做“理论上正确的事情”,你可以考虑一个额外的表,只有2柱,连接分类和他们的产品。 它会起作用,但通常这种结构只在需要多对多关系时才使用。
尽量保持它尽可能简单。 有一个“学术解决scheme”是好的,但通常意味着一点矫枉过正,更难以重构,因为它太抽象了(就像隐藏类和产品之间的关系)。
我希望这有帮助。