设计模式,用于表示派生类"container"派生自基类的容器



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

我在OptimizedTrack上运行了一个优化,在最后一次迭代中,我返回给用户干净的数据(Track(,它只有结果,没有其他数据。然而,我找不到一种方法来用OOP表达OptimizedTrack在某种程度上是Track的扩展,并为它们引入通用例程。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,它可以为数组计算,因此也可以从数组计算。

我不坚持我目前的架构。这很可能是错误的,我把它写在这里只是为了表达我所面临的问题。在这种情况下,您建议如何重构我的体系结构?

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

继承可以用于各种目的,我不想对这个问题发表意见,但大多数权威人士认为,在用于分型时,继承是最好、最安全的。简言之,子类的实例应该能够被替换为其基类的实例,而不需要";断开";程序(参见:利斯科夫替代原则(。

假设OptimizedPointPoint的一个亚型。然后,当在OptimizedPoint的实例上调用时,类Point中定义的所有方法将继续按预期运行。这意味着OptimizedPoint不能对任何这些方法调用要求任何更严格的先决条件,也不能削弱合同Point所做的任何承诺后决条件。

但是,仅仅因为OptimizedPointPoint的亚型,OptimizedPoint的容器,即OptimizedTrack,就是Point的容器,也就是Track的亚型是一种常见的谬论。这是因为您无法用OptimizedTrack的实例替换Track的实例(因为您无法将Point的实例添加到OptimizedTrack的实例中(。

所以,如果你试图遵循";良好的面向对象设计原则";,试图以某种方式使OptimizedTrack成为Track的一个子类是灾难性的,因为它肯定永远不可能是一个子类。当然,您可以重用Track来使用composition构建OptimizedTrack,即OptimizedTrack将包含在Track的实例中,length等方法将被委派到该实例中

考虑到OptimizedTrack本身就是Track,我不确定为什么要在优化过程后向客户端代码返回Track。下面是一个我认为你正在努力实现的快速示例(用Kotlin写,因为它不那么冗长(。

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

  1. 毫无问题地替换TrackOptTrack(即使优化的轨迹对象尚未计算出简化的Track对象(
  2. 通过optimize进行简化,并从OptTrack返回一个Track,而不会出现任何问题(Point上的optimize函数无关紧要,您可以在Track中返回OptPoint,因为它扩展了对象Point(
open class Point(val x: Int, val y: Int, val z: Int) {
override fun toString(): String = 
"Point(${this.x}, ${this.y}, ${this.z})"
}
data class OptPoint(val point: Point, val additional: Int): 
Point(point.x, point.y, point.z) {

override fun toString(): String = 
"OptPoint(${this.point}, ${this.additional})"

fun optimize(): Point {
return Point(this.x, this.y, this.z)
}
}
open class Track(private val points: Iterable<Point>): Iterable<Point> {

override operator fun iterator(): Iterator<Point> {
return this.points.iterator()
}

override fun toString(): String = 
"Track(${this.points})"
}
data class OptTrack(private val points: Iterable<OptPoint>): Track(listOf()) {

override operator fun iterator(): Iterator<Point> {
return this.points.iterator()
}

fun optimize(): Track {
return Track(this.points.map{ it.optimize() })
}
}
fun main(args: Array<String>) {
val track: Track = OptTrack(listOf(
OptPoint(Point(1, 2, 3), 4))).optimize()
println(track)        
// Track([Point(1, 2, 3)])
val other: Track = OptTrack(listOf(OptPoint(Point(1, 2, 3), 4)))
println(other)
// OptTrack(points=[OptPoint(Point(1, 2, 3), 4)])
}

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

最新更新