【问题标题】:design pattern to express that "container" over derived class is derived from container of a base class表示派生类上的“容器”派生自基类的容器的设计模式
【发布时间】:2020-12-04 04:33:12
【问题描述】:

我有一个 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,它可以为数组计算,因此也可以从数组计算。

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

【问题讨论】:

  • 为什么要扩展Track?如果优化完成后不再需要临时数据,则可以将optimize() 定义为Track 的方法。如果你想保留额外的数据,你可以创建一个class TrackOptimizer,它有一个Track实例作为成员(组合继承)和一个方法optimize(),它返回一个干净的Track
  • @Lini Track 包含 Points。为了优化我需要存储OptimizedPoints。在OptimizedTrack
  • 我知道你想将OptimizedPoints 存储在一个集合中。它是否必须是扩展Track 的新类OptimizedTrack?您是返回给用户Track 还是OptimizedTrack
  • @lini 不一定是扩展名;我正在为我的任务寻找可能的架构。我想将Track返回给用户。

标签: oop inheritance design-patterns containers


【解决方案1】:

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

继承可以用于各种目的,我不想对这个主题发表任何看法,但大多数权威人士认为,在用于子类型化时,继承是最好且最安全的。简而言之,子类的实例应该能够替换其基类的实例而不会“破坏”程序(参见:Liskov Substitution Principle)。

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

但这是一个常见的谬误,因为OptimizedPointPoint 的子类型,OptimizedPoint 的容器,即OptimizedTrack,是Point 的容器的子类型,即Track .这是因为您不能用OptimizedTrack 的实例替换Track 的实例(因为您无法将Point 的实例添加到OptimizedTrack 的实例中)。

因此,如果您尝试遵循“良好的面向对象设计原则”,那么尝试以某种方式使OptimizedTrack 成为Track 的子类是灾难性的,因为它肯定永远不会成为子类型。当然,您可以使用组合重用Track 来构建OptimizedTrack,即OptimizedTrack 将包含在Track 的实例中,length 等方法将被委托给该实例。

【讨论】:

  • 感谢您的回答。如果您想让观察者在不同的线程上进行跟踪(以及 OptimizedTrack),您将如何更改架构
  • 再一次,OptimizedTrack 不能是Track 的子类型的原因是因为Track 将有一个诸如add(Point) 之类的方法(我尽量不要使这个过于特定于语言) 并且这种方法对OptimizedTrack 毫无意义。但是您可以定义一个名为PointContainer 的新接口,它定义了访问其Point 成员的方法,但不提供添加新点的能力。 OptimizedTrackTrack 类都可以实现这个类!因此,您在PointContainer 上提供了一个观察者。是吗?
【解决方案2】:

考虑到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)])
    }

【讨论】:

  • “我不知道你为什么要在优化过程之后向你的客户端代码返回一个 Track”——我不想让用户知道优化的内部结构
  • 好吧,那么这种方法 ^ 应该可以解决这个问题。请注意,您可以向前迈出一步,将优化计算封装在一个策略对象中,然后将策略对象与Track 组合在一起(以消除对OptTrack 的需要),或者将其组合为OptTrack 以获得更大的灵活性将来。简而言之,您不需要 optimize() 函数 - 您可以将整个封装在对象和惰性计算中。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-14
    相关资源
    最近更新 更多