表示派生类的“容器”是从基类的容器派生的一种设计模式

问题描述

我有一个Track类,其中包含一组Point,并且可以及时表示人的位置。为了获得此结果,我运行了一个迭代优化例程,该例程组合了不同的数据。为了做到这一点,我用类Point扩展了OptimizedPoint,该类保存用于此点和当前值的优化数据。我还介绍了OptimizedTrack,它是OptimizedPoints的集合,以及与整个轨道相关的优化所需的其他数据。

我在OptimizedTrack上进行了优化,在最后一次迭代中,我返回到用户纯数据(Track),该数据仅具有结果而没有其他数据。但是,我找不到一种用OOP表示OptimizedTrackTrack的扩展并为其引入通用例程的方法。 F.e获得了一段对他们两个都应该可用的轨道的长度,因为它只使用在OptimizedTrackTrack中都可以找到的数据

现在我拥有这样的体系结构Point{x,y,z},OptimizedPoint extends Point {additional_data}. Track {array<Point>},OptimizedTrack {array<OptimizedPoint>,additional_data}。我无法理解如何表达OptimizedTrack是Track的扩展,因为我无法表达array<OptimizedPoint>扩展array<Point>。因此,我无法介绍可以为数组计算的通用例程length,因此也可以从数组计算得出。

我不坚持当前的体系结构。这很可能是错误的,我只在这里写下来表达我所面临的问题。在这种情况下,您打算如何重构我的体系结构?

解决方法

我相信,如果您遵循被认为正确使用继承来表达子类型关系的方法,那么您尝试做的基本前提就是错误的。

继承可以用于各种目的,我不希望对这个问题有所怀疑,但是大多数权威人士认为,继承用于子类型化时最好且最安全地使用。简而言之,子类的实例应该能够代替其基类的实例而不会“破坏”程序(请参阅:Liskov替代原理)。

让我们假设OptimizedPointPoint的子类型。然后,在类Point上实例上调用的类OptimizedPoint中定义的所有方法将继续按预期运行。这意味着OptimizedPoint不能在这些方法调用中要求任何更严格的前提条件,也不能削弱合同Point所制定的任何允许的后置条件。

但这是一个普遍的谬论,因为OptimizedPointPoint的子类型,而OptimizedPoint的容器(即OptimizedTrack)是Point的容器的子类型Track,即OptimizedTrack。这是因为您无法将Track的实例替换为Point的实例(因为您无法将OptimizedTrack的实例添加到OptimizedTrack的实例中)

因此,如果您尝试遵循“良好的面向对象设计原则”,那么尝试以某种方式使Track成为Track的子类是灾难性的,因为它肯定永远不会成为一个子类型。当然,您可以重用OptimizedTrack以使用合成来构建OptimizedTrack,即Track将包含在length的实例中,诸如String quote = "To be or not to be that is the question"; System.out.println(medianWordLength(quote)); static double medianWordLength(String words) { String[] parts = words.split("\\s+"); double median = 0; // sort the array based on length of the words Arrays.sort(parts,(a,b) -> Integer.compare(a.length(),b.length())); for (String v : parts) { System.out.println(v); } System.out.println("------------------"); // get the "middle" index. int idx = parts.length / 2; // adjust index based on array size if (parts.length % 2 == 0) { System.out.println(parts[idx-1] + " " + parts[idx]); median = (parts[idx-1].length() + parts[idx].length()) / 2.; } else { System.out.println(parts[idx]); median = parts[idx + 1].length(); } return median; } 之类的方法将被委派。

,

考虑到Track本身就是OptimizedTrack,我不确定为什么要在优化过程之后将Track返回到客户代码。以下是我认为您要实现的目标的简要示例(用Kotlin编写,因为不太冗长)。

如果您认为TrackPoint类型的点的可迭代对象,则可以实现更大的灵活性并解决类型问题。这样,当您从OptTrack扩展Track时,您将能够:

  1. 可以毫无问题地替换TrackOptTrack(即使您优化的跟踪对象尚未计算出简化的Track对象)。
  2. 简化optimize并从Track返回OptTrack,没有问题(optimize上的Point函数是不相关的,您可以返回{{ 1}}在您的OptPoint内,因为它扩展了对象Track
Point
,

在OOP中,您应该更喜欢对象组合而不是对象继承。对于您的问题,我认为创建指向和跟踪的界面可能会有所帮助。为了获得适当的结果,我认为,您应该创建两个接口,即IPoint和ITrack。 Track和OptimizedTrack都实现ITrack接口,并且对于常见操作,您可以创建另一个类,两个类都将请求委托给它。之后,您可以创建一个策略类,使用ITrack并返回另一个优化的ITrack。在ITrack中,您可以创建GetPath,该GetPath返回IPoint类型的对象的列表。