遵循Liskov原则的状态设计模式

4

我正在设计一个订单系统,状态设计模式似乎很合适,因为订单可以更改其状态,从而影响订单的可用功能。以下是我的基本类图:

我不喜欢这种方法,因为客户端无法看到方法是否被支持,这违反了Liskov原则。我创建了以下替代方案:

我更喜欢这个方案,但用户仍然需要检查方法是否被支持。但他们仍然可以调用不受支持的方法并获得异常。这是否仍然违反了Liskov原则?

有没有更好的设计符合Liskov原则,并防止用户调用特定状态下的无效方法?


我不明白你所说的“无法查看方法是否受支持”的意思。具体的OrderStates将全部实现OrderState方法。只有其中一些在状态当前不允许操作时才会引发异常。在我看来,这并不违反LSP。此外,我不明白当你不是直接引发UnsupportedOpEx而是先调用can方法然后再引发它时,这有何不同。在我看来,允许Order的任何消费者调用can方法更糟糕,因为它将责任移出了Order对象。 - Gordon
在类图1中,客户端无法检测特定状态下操作是否有效。因此,无法知道应该显示付款按钮还是取消按钮。在我对LSP的理解中,抛出NotSupportedException是破坏LSP的常见方式。这是因为子类型没有做到它所说的,因此子类型不能替换其基类型。 - Roy van der Tuuk
具体的 OrderStates 将全部实现 OrderState 接口,因此它的方法也将被实现。所有这些方法都可能根据接口契约引发异常。因此,您可以用另一个具体的 OrderState 替换任何一个。因此,您不会违反 Liskov 的替换原则。您可以使用相同的模式来绘制按钮。只需将按钮渲染器传递给 OrderState,并从具体的 OrderState 返回正确的按钮,例如,Confirmed 将告诉 ButtonRenderer 渲染一个 Pay 按钮等。 - Gordon
我查了一下LSP的定义:“如果S是T的子类型,则可以用类型S的对象替换类型T的对象(即,可以用类型S的对象代替类型T的对象),而不会改变该程序的任何期望属性(正确性、执行任务等)。” 由于您在订单上调用支付方法并期望支付订单,因此违反了正确性。相反,当前状态将永远无法支付。因此,这将破坏系统的正确性。 - Roy van der Tuuk
请参见http://programmers.stackexchange.com/questions/181922/does-the-state-pattern-violate-liskov-substitution-principle,特别是http://programmers.stackexchange.com/a/181941/49178 - 如果异常可以预料,那么正确性不会被违反。否则,引发异常的任何方法都将违反LSP。 - Gordon
顺便说一句:我可能会将OrderState方法简化为只有advance() - Gordon
1个回答

0
你展示的不是状态模式。状态模式会在对象内部状态改变时改变其行为。例如,当你切换开关时,灯开或关取决于其状态(相同方法上的不同行为)。
通过这个订单接口(4个不同的方法),我看不到引入状态模式的任何好处。这只会无缘无故地使事情复杂化。但我不知道所有的细节,所以下一步该怎么做就由你决定了。
请查看此链接以查看状态模式实现的示例 https://sourcemaking.com/design_patterns/state

我在设计中使用状态模式的原因是因为订单会改变状态,从而改变其行为。如果我创建分离的订单类,则订单无法在运行时更改其行为。您有其他模式的建议吗? - Roy van der Tuuk
最简单的解决方案是使用一个Order模型,其中包含4个不同的方法,当您尝试在对象处于不适当状态时使用方法时,会抛出异常。通常,当我不知道所有细节时,我会选择最简单的解决方案,以便稍后更容易地重构。但这完全取决于您的应用程序/代码中Order的使用情况。例如,如果您有一个Order接口,其中包含一个proceedToTheNextState方法,您可以创建4个状态:确认,取消,支付,发货,这些状态执行与状态相关的所有工作并转换到下一个状态。 - Ihor Burlachenko
此外,您可能有许多不同的原因想要拆分订单行为逻辑。而且,由此产生的解决方案可能并不是状态模式的确切实现。如果一切都是有原因的,那就没问题了。如果您在子类中遵循接口和方法定义,就不会破坏LSP。 - Ihor Burlachenko

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