当前位置: 首页 > 知识库问答 >
问题:

如何避免用一个实现多个接口的类打破Liskov替换原则?

潘哲
2023-03-14

给定以下类:

class Example implements Interface1, Interface2 {
    ...
}

当我使用Interface1实例化类时:

Interface1 example = new Example();

...然后我只能调用Interface1方法,而不能调用Interface2方法,除非我强制转换:

((Interface2) example).someInterface2Method();

当然,为了确保此运行时的安全性,我还应该使用instanceof复选框将其包装起来:

if (example instanceof Interface2) {
    ((Interface2) example).someInterface2Method();
}

我知道我可以有一个包装器接口来扩展这两个接口,但是我最终可以有多个接口来满足可以由同一个类实现的所有可能的接口排列。所讨论的接口不能自然地相互扩展,因此继承似乎也是错误的。

当我询问运行时实例以确定其实现时,instanceof/cast方法是否会破坏LSP?

无论我使用哪种实现,无论是在糟糕的设计还是在使用中,似乎都会产生一些副作用。

共有3个答案

吕永嘉
2023-03-14

你有两个不同的选择(我打赌还有更多)。

第一个是创建自己的接口,它扩展了另外两个:

interface Interface3 extends Interface1, Interface2 {}

然后在整个代码中使用:

public void doSomething(Interface3 interface3){
    ...
}

另一种方法(我认为更好的方法)是对每个方法使用泛型:

public <T extends Interface1 & Interface2> void doSomething(T t){
    ...
}

后一个选项实际上比前一个选项限制更少,因为泛型类型T会得到动态推断,从而导致耦合更少(类不必实现特定的分组接口,如第一个示例)。

卢书
2023-03-14

您的类可以很好地实现多个接口,并且它不违反任何OOP原则。相反,它遵循的是界面隔离原则。

令人困惑的是,为什么会出现这样一种情况,即Interface1类型的东西需要提供someInterface2Method()。这就是你的设计错误的地方。

用一种稍微不同的方式来思考:想象你有另一种方法,void method1(Interface1 Interface1)。它不能期望interface1也是Interface2的一个实例。如果是这样的话,参数的类型应该有所不同。您所展示的示例正是这样的,它有一个Interface1类型的变量,但期望它也是Interface2类型的变量。

如果您希望能够调用这两个方法,您应该将变量的类型示例设置为示例。这样就可以完全避免instanceof和类型转换。

如果你的两个接口Interface1Interface2不是松散耦合的,并且你经常需要从这两个接口调用方法,也许分离接口不是一个好主意,或者你想有另一个扩展这两个接口的接口...

通常(尽管不总是如此),instanceof检查和类型转换通常表明存在一些OO设计缺陷。有时,该设计适合于程序的其余部分,但在一个小案例中,键入cast比重构所有内容更简单。但如果可能的话,作为设计的一部分,首先你应该努力避免它。

曾阳飙
2023-03-14

我知道我可以有一个包装器接口来扩展这两个接口,但是我最终可以有多个接口来满足可以由同一个类实现的所有可能的接口排列

我怀疑,如果你发现你的很多类实现了不同的接口组合,那么要么:你的具体类做得太多了;或者(不太可能)您的接口太小、太专业,以至于无法单独使用。

如果您有充分的理由要求某些代码同时是一个Interface1和一个Interface2,那么绝对可以做一个扩展两者的组合版本。如果你努力想出一个合适的名字(不,不是foAndBar),那么这表明你的设计是错误的。

绝对不要依赖铸造任何东西。它只能作为最后手段使用,通常仅用于非常特定的问题(例如序列化)。

我最喜欢和最常用的设计模式是装饰模式。因此,我的大多数类只会实现一个接口(除了更通用的接口,如可比)。我想说,如果你的类经常/总是实现多个接口,那么这就是代码的味道。

如果您正在实例化对象并在相同的范围内使用它,那么您应该只写

Example example = new Example();

很明显(我不确定这是否是你的建议),在任何情况下你都不应该写这样的东西:

Interface1 example = new Example();
if (example instanceof Interface2) {
    ((Interface2) example).someInterface2Method();
}
 类似资料:
  • 1) LSP是否也适用于接口,这意味着我们应该能够使用实现特定接口的类,并且仍然能够获得预期的行为? 2)如果确实如此,那么为什么编程到接口被认为是一件好事(BTW-我知道编程到接口会增加松散耦合),如果反对使用继承的主要原因之一是由于不遵守的风险LSP?也许是因为: a) 松耦合的好处大于不遵守LSP的风险 b)与继承相比,类(实现接口)不附着于LSP的可能性要小得多 谢谢你们

  • 问题内容: 我的情况与Code Complete中Steve McConnell 提到的情况非常相似。我唯一的问题是基于车辆,而三轮车恰好是根据法律,属于汽车。到目前为止,汽车只有四个轮子。无论如何,我的域都不必要地复杂,因此很容易遵循下面的cats示例。 对重写例程并且在派生例程中不执行任何操作的类要保持怀疑。这通常表明基类的设计存在错误。例如,假设您有一个Cat类和一个例程Scratch(),

  • 我在理解这两个原则时有些困难。这是一个有点长的阅读问题,所以要有耐心。 假设我们有一个类 和接口 然后我们创建了两个子类 现在我们将使implements 并在儿童课程中做出改变 并为该结构创建测试函数(如下LSP): 在这里我想停下来,因为实际上在下一步我被绊倒了。如果我们要创建第三个类? 圆没有边,所以为这个孩子实现听起来很可笑。好的,我们可以只将实现移到四边形和三角形,但在这种情况下LSP将

  • 如何让Spring在运行时使用名称或某个属性来使用单个实现类?

  • 问题内容: 一个类文件可以实现几个接口?类文件使用的接口数量是否有限制?提前致谢。 问题答案: 出于所有实际目的,一个类可以实现的接口数量没有限制,但是java不允许您从多个超类继承。 但是,如果您确实想要nitpick,则可以说一个类可以实现的接口数量受接口ID可以以java字节码表示的最大值限制,或者必须由实现这些接口的代码存储量来限制,或者存储字节码的硬盘空间量。这些都是愚蠢的论点。显然,由