对象与数据结构

在文章的翻译(建议如下)中,罗伯特·马丁(Robert Martin)似乎以与耶戈尔·布加坚科(Yorgor Bugaenko)关于ORM的讨论非常相似的思想开始,但其他人得出了结论。 就个人而言,叶戈尔的方法给我留下了深刻的印象,但我认为马丁会更详细地揭示这个话题。 在我看来,所有曾经考虑过ORM应该占据什么位置以及通常为何需要打开所有字段的对象的人都应该征求他的意见。 本文以“对话”类型撰写,其中经验丰富的程序员与经验较少的人讨论问题。


什么是课程?

类是许多类似对象的规范。


什么是物体?

对象是对封装的数据执行操作的一组功能。


或者说一个对象是一组函数,这些函数对隐含存在的数据执行操作

在意义上“暗示”?


一旦对象具有功能,就可以假定那里也有数据,但是不能直接访问数据,并且从外面根本看不见它们。

数据不是对象中的吗?


也许他们在,但是说他们必须在那的规则却没有。 从用户的角度来看,一个对象不过是一组功能。 这些功能所使用的数据必须存在,但是用户不知道这些数据的位置。

好吧,让我们说。


那么,什么是数据结构?

数据结构是相关项目的集合。


或者,换句话说,数据结构是一组元素,这些元素可与功能一起工作,隐含地暗示其存在。

好吧好吧 知道了 不能在这些结构内部定义与数据结构一起使用的功能,但是从数据结构的存在本身就可以得出结论,肯定有某些东西可以使用它们。


对啊 那两个定义呢?

从某种意义上说,它们是彼此对立的。


真的 他们互相补充。 像手和手套。
  • 对象是一组与隐式隐含存在的数据元素一起使用的函数。
  • 数据结构是一组功能起作用的数据元素,其存在被隐式隐含。

哇! 因此,事实证明对象和数据结构不是一回事!


对啊 数据结构是DTO。

数据库中的表也不是对象,对吗?


再次正确。 数据库包含数据结构,而不是对象。

等一下 ORM不会将表从数据库映射到对象吗?


当然不是 您不能将数据库表映射到对象。 数据库中的表是数据结构,而不是对象。

那ORM会做什么?


他们将数据从一种结构传输到另一种结构。

因此,它们与对象无关吗?


没事 严格来说,在技术上将关系数据映射到对象的技术之类的ORM并不存在,因为不能将表从数据库映射到对象。

但是他们告诉我,ORM收集业务对象。


否,ORM从与业务对象一起使用的数据库中检索数据

但是这些数据结构不属于业务对象吗?


也许他们得到了,也许没有。 ORM对此一无所知。

但是区别纯粹是语义上的。


不不 有深远的影响。

例如?


例如,数据库模式的设计和业务对象的设计。 业务对象定义业务行为。 数据库模式定义业务数据结构。 这些结构受到非常不同的力的限制。 业务数据结构不一定是业务行为的最佳结构。

哎呀 这是无法理解的。


这样想吧。 数据方案不是为单个应用程序设计的,而是打算在整个企业中使用。 因此,数据结构是几个不同应用程序之间的折衷方案。

这是可以理解的。


好啊 现在考虑每个应用程序。 每个应用程序的对象模型描述了如何构造应用程序的行为。 每个应用程序都有其自己的对象模型,以更好地匹配应用程序的行为。

嗯,我明白了。 由于数据方案是不同应用程序之间的折衷方案,因此该方案将不会落在每个单独应用程序的对象模型上。


对! 对象和结构仅限于不同的事物。 他们很少能融合在一起。 人们称此为对象关系阻抗的不匹配。

我记得的东西。 但是似乎阻抗失配只是使用ORM校正的。


现在您知道事实并非如此。 对象和数据结构之间的阻抗不匹配是互补的,而不是同构的。

什么啊


他们是对立的,不是相似的东西。

相反吗


是的,在一个非常有趣的意义上。 您会看到,对象和数据结构意味着完全相反的控制结构。

什么啊


考虑一下实现某种通用接口的一组类。 例如,想象一下代表二维图形的类,其中包含用于计算图形的面积和周长的函数。

在所有示例中,形状对对象的编码量为多少?


让我们看一下两种不同类型的形状:正方形和圆形。 显然,用于计算这些类别的面积和周长的函数使用不同的数据结构。 还应理解,使用动态多态性来调用这些操作。

请放慢脚步,什么都不清楚。


有两种不同的计算面积的函数,一个用于平方,另一个用于圆。 当调用一个函数来计算特定对象的面积时,正是这个对象决定了要调用哪个特定函数。 这称为动态多态性。

知道了 当然可以 对象知道如何实现其方法。 自然地。


现在,让我们将这些对象转换为数据结构。 我们使用歧视联盟。

歧视什么?


歧视工会。 好吧,C ++,指针,union关键字,用于确定结构类型的标志,歧视联合。 在我们的例子中,这只是两个不同的数据结构。 一为广场,一为圆。 圆有一个中心点和一个半径。 还有一个类型代码,从中可以理解它是一个圆。

带有代码的字段将是枚举?


好吧,是的。 正方形将具有左上角和边的长度。 并且还枚举以指示类型。

知道了 将有两个带有类型代码的结构。


对啊 现在让我们看一下该区域的功能。 可能会有一个开关,对吧?

好吧 当然要上两节课。 广场和圆的分支。 对于周边,您还需要一个类似的开关。


再说一次。 现在考虑这两种情况。 在带有对象的场景中,区域功能的两个实现彼此独立,并且(在某种意义上)直接属于该类型。 正方形的面积的函数属于正方形,而确定圆的面积的函数属于圆。

好的,我知道您要达成的目标。 在具有数据结构的场景中,一个区域的两个函数实现都在同一函数中,它们并不“属于”该类型(无论该词是什么意思)。


进一步更好。 对于对象,如果需要添加Triangle类型,应更改什么代码?

完全不要更改任何内容。 只需创建一个新的Triangle类。 尽管没有,但是您可能需要修复创建对象的代码。


对啊 因此,在添加新类型时,更改可以忽略不计。 现在假设您需要添加一个新功能-例如,一个确定中心的功能。

然后,您必须将其添加到所有三种类型,圆形,正方形和三角形。


好啊 事实证明,添加新功能很困难,因为您必须在每个类中进行更改。

但是对于数据结构,一切都不同。 为了添加一个三角形,您必须更改每个函数以添加分支以处理每个开关中的三角形。


对啊 添加类型很困难;您必须编辑每个功能。

但是,为了为中心添加功能,无需进行任何更改。


是的,添加功能很容易。

哇 事实证明,这两种方法是完全相反的。


绝对可以。 总结一下

  • 向类添加新功能很困难,您必须在每个类中进行更改
  • 向数据结构添加新函数很简单,您只需要添加一个函数,就无需更改其他任何东西
  • 向类添加新类型很简单,您只需要添加一个新类
  • 很难为结构添加新类型;您需要修复每个函数

是的 相反。 与之相反。 即,如果预先知道需要添加新功能,则使用数据结构是方便的。 但是,如果您事先知道必须添加新的类型,则需要使用类。


好观察! 但是今天我们需要考虑另一件事。 还有一点,数据结构和类彼此相反。 依赖关系。

上瘾?


是的,源代码中依赖关系的方向。

好吧,我会问。 有什么区别?


让我们看一下结构的情况。 每个函数都包含一个开关,该开关根据联合中的类型代码选择所需的实现。

是的,是的。 那又怎样


让我们看一下该区域的函数调用。 调用代码取决于区域的功能,区域的功能取决于每个特定的实现。

你说“依赖”是什么意思?


想象一下,一个区域的每个函数实现都分配给一个单独的函数。 也就是说,将有circleArea,squareArea和三角形Area函数。

好吧,事实证明,在switch分支中将仅存在对这些函数的调用。


想象一下,这些功能位于不同的文件中。

然后在带有开关的文件中将导入或使用或包含具有功能的文件。


对啊 这是源代码级别的依赖项。 一个来源取决于另一来源。 如何处理这种依赖性?

带switch的源代码取决于实现所在的源代码。


调用该区域的函数的代码呢?

调用代码取决于带有switch的代码,该代码取决于所有实现。


对啊 在所有来源中,箭头都指向从调用代码到实现的调用方向。 因此,如果您想对这些实现进行微小的更改...

好吧,好吧,我明白你的意思了。 任何实现的更改都将需要使用switch重新编译所有文件,这将导致以下事实:调用此switch的所有内容都将重新编译,例如,在我们的例子中为area函数。


是的 至少对于使用文件修改日期以便了解需要重建内容的语言而言,情况将会如此。

这些通常都是带有静态类型的系统,对吗?


是的,还有其他一些没有它的系统

这需要大量重建。


重做很多。

好的,但是就班级而言,是相反吗?


是的,因为调用该区域的函数的代码取决于接口,而实现也取决于此接口。

知道了 Square类的代码将使用Shape接口导入或使用或包括一个文件。


对啊 实现文件中的箭头指向与调用相反的方向。 它从实现代码定向到调用代码。 至少对于静态类型的语言将是这种情况。 对于动态类型的语言,调用该区域的函数的代码根本不依赖任何内容,因为链接在运行时发生。

是的,好的 也就是说,如果您对其中一种实现进行了更改...


仅需要通过这些更改来重建并重新安装代码。

这是因为依赖关系与调用方向相反。


是的,我们称之为依赖倒置。

好吧,让我们总结一下。 类和数据结构在三种意义上彼此相对。


  • 这些函数显式位于类中,并且您只能猜测数据的存在。 数据结构显式存在于数据结构中,您只能猜测哪些功能可用。
  • 对于类,添加类型很简单,但是添加函数很困难。 对于结构,添加功能很容易,但是添加类型很困难。
  • 数据结构导致调用代码的重新编译和重新分发。 类隔离调用代码,不需要重新编译和重新部署它。

是的,没错。 每个设计师和软件架构师都应牢记这一点。

Source: https://habr.com/ru/post/zh-CN477712/


All Articles