我在程序中遇到设计问题,原因是abstract
base class
有一个方法有一个位置(因此是可选的)参数。
我们假设此课程为A
,方法为void f(x, [y]);
。现在,y
是可选的,因为我已经知道A
的某些子类将使用它,其中一些不会。
实际问题违反了Liskov替换原则:在需要y
的子类中,如果未提供y
,则必须抛出异常,而在A.f
中(是没有实现的)我没有抛出任何异常。
A
的设计也很糟糕,因为我提供了一些方法f
,某些子类确实需要它,其中一些需要稍微不同的版本。显然,我应该尽可能地设计我的接口(接口隔离)。
这实际上是一个普遍问题,不仅与Dart有关。
那么,如何处理可选参数以免违反Liskov替换原则?特别是,你如何处理我的情况,以至于我也没有违反界面隔离原则?
我现在看到的唯一合理的解决方案(针对我的特定问题)是使当前的子类扩展A
并且实际需要y
f
实际扩展(或实现) ,如果A
实际上是一个接口)另一个具有方法f(x, y)
的基类,这是两个参数都需要的地方。但是如何处理可选参数的问题仍然存在!
答案 0 :(得分:2)
在我阅读它时,问题是你想要的子类实际上不能替代超类。您希望两个类A
和B
都实现相同的API,即使这些类不是真正可互换的。
其中一个只使用一个参数(可以说,只应该接受一个参数),另一个参数需要两个参数。这两个类只是不兼容,所以添加一个公共超类,以某种方式抽象出不合理的opreations注定会失败。
也就是说,如果您已经知道A
的某些子类不会使用foo
的第二个参数,那么为什么它们是A
的子类?因为作为A
的子类,他们应该接受A
接受的任何参数,并以与A.foo
文档的契约一致的方式使用它。
问题不是可选参数,它是超类中的可选参数。如果参数在超类中是可选的,则它在所有子类中也必须是可选的,因为子类需要以与超类相同的方式调用 s 。取(x, [y])
的函数不能被一个只取一个或两个参数的函数替换,反之亦然。
子类必须允许 more 而不是超类,并且从可选的参数变为不可选的允许 less 。
如果你有课程
class X { foo(x) {} }
class Y { foo(x, y) {} }
class Z implements X, Y { foo(x, [y]) {} }
然后它起作用,因为Z
允许超过X
或Y
。使用Z
作为超类而不是子类将不起作用,它与声音和安全的方向相反。
答案 1 :(得分:0)
我没有真正看到LSP和可选参数之间的连接。你会违反原则,取决于你的代码,以及你对params的处理方式,而不是因为语言为你提供的选项。
在你的例子中,我会说至少可以说你违反了LSP,因为A
是抽象的(因此你无法实例化它),你不能直接调用f
,因为它不是实现。
让我们省略该部分,并说你有类A
和子类B
(都具体),并且都实现了方法f(x, [y])
。
然后假设a
为A
的实例,b
为B
的实例。鉴于[x
,y
]的任何值,如果您使用a.f(x,y)
的任何地方,您可以使用((A)b).f(x,y)
并获得完全相同的结果,然后您不会违反LSP。
无论如何,如果你觉得你可能违反了LSP,那么你必须问自己是否真的需要层次结构。不是将B
声明为A
的子类,也许您可以使用A
和B
使用常用方法实现接口,并移动A
中的代码} B
用于您可以从A
和B
拨打的其他班级(请参阅https://en.wikipedia.org/wiki/Composition_over_inheritance)。
答案 2 :(得分:0)
Liskov Substitution Principle的基本格言是:
使用指针或对基类的引用的函数必须能够使用派生类的对象而不知道它。
换句话说: - 课程应根据行为而非属性进行建模; - 数据应基于属性而非行为建模。
所以在你的情况下,我说它是违规的,因为可选参数不会成为其余子类行为的一部分。最好将它作为使用它的子类的一部分。
这是一张来自惊人SOLID principles的精彩图片!