适当的面向对象建模对应关系

3
有一件事情在我的编程中经常出现,那就是两个东西在某种角度上是相同的,但从另一个角度来看却是不同的。比如说,假设你建立了一个由火车站通过火车连接起来的图形,那么类Vertex和RailStation有时候是相同的,而有时候又不同。
所以,假设我有一个非常代表火车站和火车的图形。然后我把这个图形交给另一个对象,它删除了一些顶点,然后我想让相应的铁路站消失。
我不想将铁路站作为顶点的“属性”,因为它们并不是。此外,问题是对称的:如果我删除了一个铁路站,我希望相应的顶点也消失。那么,面向对象的正确建模或者对应方式是什么呢?如果最终的使用方法简单易懂,我愿意写一些支持方法或者类来实现。
我目前正在使用Smalltalk编程语言,但我认为这个问题并不是特定于Smalltalk的。我提到它只是因为在Smalltalk中,你可以像检查调用堆栈一样做一些很酷的技巧,这可能在这种情况下有所帮助。
更新: 好吧,RailStations并不是Vertices!它们是吗?
好的,让我们考虑真正的代码,就像答案中要求的那样。让我用一个孩子和父母的例子来模拟。这应该是最简单的事情,对吧?孩子也应该知道他们的父母,所以我们有一个双向链接的树形结构。为了更容易地解除父母与孩子之间的联系,我将父母与孩子之间的链接建模为一种关系,并具有父母和孩子两个属性。
因此,我可以像这样实现parent>>removeChild:
removeChild: aChild
    (parent relationshipWith: aChild) disband.

因此,一个父级对象具有关系的集合,而不是子代。但是每个关系对应着一个子代。现在我想要做这些事情:
parent children removeAllSuchThat: [:e | e age < 12]

这应该删除关系子元素。

在这里,关系和子元素在某种程度上是对应的。那么,我现在该怎么办?别误解我的意思,我完全知道我可以不引入关系类就解决问题。但事实上,父母和子代确实分享一种关系,为什么不对其进行建模,并将其用于帮助较不强制性地解散双链接呢?


我必须评论一下你的问题:RailStations不是Vertices吗?从你的问题中我没有看到任何暗示表明在面向对象的方式中,vertices和railStations是不同的实体。唯一的区别在于,vertices只是railStations的视觉表示。这样对吗? - zidane
5个回答

3
在您的问题领域中,车站不是一种顶点吗?这种情况下,为什么不从顶点派生出车站?
请注意短语“在您的问题领域中”的使用。您的问题似乎涉及图形中出现的铁路车站的使用。因此,在该领域中,车站是顶点。如果是不同的问题领域,比如铁路车站架构数据库,它们可能不是。大多数现代语言都支持某种名称空间的概念,以允许您在不同的领域中拥有具有相同名称的不同实体。
关于您的父/子问题,再次您过于笼统。如果我正在建模数学表达式和子表达式,则如果我删除父项,则希望删除并释放所有子表达式。另一方面,如果我正在建模英国人口的法律责任关系,那么当一个责任被解除(比如因为离婚),我只想删除关系,而不是删除/释放孩子,因为孩子有自己独立的存在。

关于子类的问题,在某些情况下它们是相应的,而在其他情况下则不是。这完全是我的观点,这就是为什么我会避免简单地使用继承。是否识别取决于上下文。难道没有一种适用于上下文的建模方法吗?如果我处于对应的上下文中,如何对其进行建模?我想到的一个想法是创建一种特殊类型的标识集合,其中包含Railstations和Vertices的成对出现,并且可以切换为服务于Railstations或Vertices。因此,删除RailStation也将删除Vertex。 - nes1983
当然,通过删除子项,我指的是:将其从其父项中删除。而不是从系统中删除。 - nes1983
就像我在答案中所说的那样,您正在尝试为两个不同的问题域创建一个解决方案。除了非常基本的类型(例如整数、字符串、列表等),这很少是一个好主意。 - anon

2
似乎你只是想让RailStation继承自Vertex(即is-a关系)。请参考这个有关继承的Smalltalk教程。这样,如果你有一个由RailStations组成的图形,处理(通常)由Vertexes组成的图形的对象会自然而然地处理事情。
如果这种方法行不通,请更具体(最好附带真实代码)。

2
根据您所描述的问题,车站与顶点一一对应,删除一个车站应该自动删除相应的顶点(反之亦然)。您还提到要构建“由火车连接的铁路站点图”,显然这是指一个以站点为顶点、以火车为边的图。
那么,车站何以不是顶点?如果车站只存在于顶点中,而顶点也只存在于车站中,那么将它们作为两个不同但相关的实体保留下来有什么好处?
根据我理解,车站即顶点,继承就是模拟这种情况的方式。

嗯,铁路站只是一个例子。我经常遇到对应关系,但不知道如何处理,虽然继承可以提供一种廉价的解决方法,但总感觉很廉价和欺骗。如果我构建一个Seaside组件图,我不确定是否想要改变整个Seaside层次结构。我真正想要的是在某些上下文中将事物粘合在一起,在其他上下文中将它们分开。 - nes1983
在这种情况下,听起来需要使用聚合/委托 - railstation/seaside有一个顶点,该顶点在rs/ss构造函数中创建并在其析构函数中销毁。您只需要记住,在这种情况下不要手动销毁顶点,除非您创建了一个OwnedVertex子类,在死亡之前通知其所有者,以便rs/ss可以随之自我销毁。 - Dave Sherohman

1

拥有一个关系对象是个好主意。

我认为这里适当的问题是“应该如何使用它?”。

可能父类和子类都继承自同一个Person超类,所以他们会有一些共同的属性,例如年龄。

在我的想法中,我可以看到以下内容:父母和孩子对象必须互相了解,因此两个类都必须保持与同一个关系的链接。 关系对象在单个父母和若干个孩子之间保持一对多的关系,并且它将保持对每个人物对象的引用。

这样,您可以在Relationshp对象内实现整个解散逻辑,更或者根据您的需要进行更复杂的操作。您可以查询Relationship对象以了解哪些家庭成员符合您的要求以执行某些操作。您可以安全地解除关系(并销毁),因为它将知道所有成员,并要求他们断开引用,然后准备销毁,或要求某个成员离开家庭,使关系对象保持活动状态。

但这还不是全部。关系应该真正成为一个超类,由分层关系和同级关系(或朋友关系)扩展。

这种特殊化让您可以在完全遍历的方式下,将父母和孩子链接到其他层次结构中。

这背后的真正概念是,你的关系对象是查询和组织整个人物对象(或顶点对象)的关键,以可扩展和结构化的方式,因此你最终得到的整个数据域可以按照任何你喜欢的方式使用,无论你想要解散群组还是在它们之间走特定的路径(或铁路)。
抱歉用了这么多隐喻。

实际上,我非常赞同你的思路,它澄清了我心中许多疑惑。虽然我仍不确定如何做到这一点,但是我确实看到了查询方面的影响(事实上,这就是我首先提出问题的原因)。 - nes1983
好的尼科,如果你需要更多帮助,请告诉我。我不懂Smalltalk,但我懂Objective-C和C++。也许我们可以用没有直接语言含义的方式来表达面向对象的概念。 - Federico Zancan

1

看一下Fame,访问http://www.squeaksource.com/Fame.html

我们使用Collection的专用子类,在添加或删除元素时更新相反的端点。此外,您可以使用pragma为类注释关系。 Fame框架使用这些pragma来执行各种好玩的操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接