【问题标题】:Can I apply here Liskov substitution principle我可以在这里应用里氏替换原则吗
【发布时间】:2014-09-22 11:39:15
【问题描述】:

我有两个由以下类表示的数据模型:

1) ImagesSet - 一个拥有 2DImage 的对象,每个 2DImage 都有自己的位置(origin(3DPoint), x-,y-axes(3DVector) 和沿 x 和 y 轴的尺寸(以像素为单位)),但相同的像素大小(例如以 mm 为单位),x 和y轴(90度)

这个对象有以下方法(伪代码):

  • 添加图像(2D图像);
  • RemoveImage(ImageIndex);
  • 数字GetNumberOfImages();
  • 2DImage Get2DImage(ImageIndex);

2) 3DImage - 与第一个类似但有以下限制的对象: 它只能存储具有相同 x、y 轴和沿 x 和 y 轴的尺寸的 2D 图像。

在这种情况下从 ImagesSet 派生 3DImage 是否正确? 从我的角度来看 3DImage “是一个” ImagesSet (但有一些小的限制) 我可以在这里应用 Liskov 替换原则吗?

在这种情况下,如果我们尝试添加具有另一个 x,y 轴的图像 - 方法 AddImage 将抛出异常或返回错误。

提前致谢, 谢尔盖

【问题讨论】:

    标签: oop design-patterns object-oriented-analysis


    【解决方案1】:

    我同意 maxim1000 的观点,即 LSP 将被违反,因为派生类添加了基类中不存在的限制。如果您仔细查看您的描述,您会发现问题可以颠倒过来:ImageSet 可以派生自 3DImage 吗?

    您的情况有点类似于椭圆圆问题。哪一个派生自另一个?圆是带约束的椭圆,还是椭圆是带附加半径的圆?关键是两者都是错误的。如果您将椭圆限制为相等的半径,则尝试设置不同值的客户端会收到错误。

    否则,如果我们说椭圆只是一个约束较少的圆,我们就会犯一个更微妙的错误。假设形状可能不会超出屏幕的边界。现在假设一个圆被一个椭圆代替。根据测试的坐标,形状可能会在不更改客户端代码的情况下脱离屏幕区域。这正是违反 LSP 的。

    结论是——圆和椭圆是分开的类; 3DImage 和 ImageSet 是独立的类。

    【讨论】:

    • 顺便说一句,这个问题只发生在 Circle 和 Ellipse 的可变接口上。如果我们在其接口中只有读取方法,则可以从椭圆派生圆(如果有用的话)。
    【解决方案2】:

    可能只有我一个人,但每当我听到“派生或不派生”时,我的第一反应是“不派生”:)

    在这种情况下有两个原因:

    1. 正是因为这些“小限制”,才违反了 LSP。因此,在您的基类中有AddImage 允许添加具有任何方向的图像之前,3DImage 不是 ImagesSet。算法将无法声明它们需要此功能(并且 cmets 不是一个好地方 :)),因此您将不得不依赖运行时检查。仍然可以以这种方式进行编程,但这对开发人员来说将是一个额外的开销。

    2. 每当您创建一些抽象时,了解创建它的确切原因很重要。通过推导,您可以隐式创建一个抽象——它是3DImage 的接口。而不是这个,最好显式地创建这个抽象。创建一个接口类,列出对能够在两种数据结构上工作的算法有用的方法,并使ImagesSet3DImage 实现该接口可能添加一些其他方法。

    附: 很可能AddImage 将成为这些附加方法之一 - 在ImagesSet3DImage 中有所不同,但这取决于...

    【讨论】:

      【解决方案3】:

      亲爱的 maxim1000 和 sysexpand,

      感谢您的回答。我同意你的看法。现在很明显违反了 LSP,在这种情况下,我无法从 ImagesSet 派生 3DImage

      我需要通过以下方式重新设计解决方案:

      2DImage 将包含:

      • 2DDimension
      • 像素大小(以毫米为单位)
      • 像素数据

      2DImageOrientated 将派生自 2DImage 并将包含新数据:

      • 3DPoint 原点,
      • 3DVector x、y 轴

      我将创建纯接口IImagesSet

      • 数字GetNumberOfImages()
      • RemoveImage(ImageIndex)
      • 2DImageOrientated Get2DImage()

      ImagesSet 将派生自 IImagesSet 并将包含以下内容:

      • 矢量2DImageOrientated>
      • Add2DImage(2DImageOrientated)
      • 数字GetNumberOfImages()
      • RemoveImage(ImageIndex)
      • 2DImageOrientated Get2DImage()

      3DImage 也将派生自 IImagesSet 并将包含以下内容。

      • 矢量2DImageOrientated>
      • Add2DImage(2DImage)
      • SetOrigin(3DPoint)
      • SetXAxis(3DVector)
      • SetYAxis(3DVector)
      • 数字GetNumberOfImages()
      • RemoveImage(ImageIndex)
      • 2DImageOrientated Get2DImage()

      在这种情况下,我认为没有违反 LSP。

      【讨论】:

        猜你喜欢
        • 2020-04-05
        • 2017-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-26
        相关资源
        最近更新 更多