【问题标题】:Java - violation of Liskov substitution principle?Java - 违反里氏替换原则?
【发布时间】:2020-11-08 22:45:05
【问题描述】:

我有以下代码:

interface Ops{
    void remove();
}

interface BeforeRemove{
    void doBeforeRemove();
}

class A implements Ops{
    @Override
    public void remove() {
        System.out.println("REMOVED A");
    }
}

class B implements Ops, BeforeRemove{
    @Override
    public void remove() {
        System.out.println("REMOVED B");
    }

    @Override
    public void doBeforeRemove() {
        System.out.println("SOMETHING TO DO BEFORE REMOVE");
    }
}

public class Proba2 {
    public static void main(String[] args) {
        List<Ops> ops = List.of(new A(), new B());
        for(Ops o : ops){
            if(o instanceof BeforeRemove br){   //is this a bad thing to do?
                br.doBeforeRemove();
            }
            o.remove();
        }
    }
}

这个转换为BeforeRemove 是否违反了 Liskov 替换原则?如果是 - 为什么会这样?我在某处读到,我们可以强制转换为某种类型的唯一时间是当我们知道那将是那种类型但编译器不知道时。这里我和编译器都不知道。

另一种选择是将 doBeforeRemove 方法移动到 Ops - 但是我可能会有很多空方法 - 这对我来说似乎也不合适。

【问题讨论】:

  • 在这种情况下,您没有做任何违反 LSP 的事情。但是,如果一个对象突然需要在remove() 之前调用doBeforeRemove(),那么以后很容易导致违规,即使Ops 说如果没有这样的调用,该对象应该是可移除的。您是否考虑过在您的界面中使用default 方法?
  • 我的意思是,理论上大多数 OOP 方法是使用访问者模式,并为 Ops, BeforeRemoveOps 提供不同的实现,但这会使事情变得有点复杂。我只是想问一下instanceof,因为我读到它是使用 OOP 时的代码异味。
  • 如果 B extends A implements BeforeRemove 和 B 的 remove() 需要首先调用 doBeforeRemove(),那么它违反了 LSP,因为它不能再代表 A。但是,现在它只是一个代码味道而不是 LSP 违规。我肯定会说空方法是一种更清洁、更合适的方法。这并不难看或乏味,因为自 Java 8 以来您不必实际编写未使用的方法,甚至在此之前,您有抽象类来简化它。
  • @BojanVukasovic 请问这里为什么需要doBeforeRemoveOps 实现者将知道何时已经调用了 remove,客户端也是如此。你想解决什么问题?拥有这两个操作会迫使客户知道应该以什么顺序调用它们,这不是很吸引人。
  • @plalx 是的,我猜这里doBeforeRemove 应该隐藏在remove 后面。我记得instanceof 是一种代码异味,这更像是一个问题,但后来想看看在这种情况下可以做什么——比如添加基本空方法的替代方法,这也不理想......

标签: java oop design-patterns solid-principles liskov-substitution-principle


【解决方案1】:

LSP 指的是子类和超类(或接口和实现)的行为。它不涉及用法本身,这意味着您使用这些类的方式与 LSP 无关,只有类的定义才起作用。

因此,您问题中的代码不违反 LSP。

也就是说,instanceof 是一种“设计气味”。正如@that other guy 所说,可以使用 Java 8 的默认方法消除它:

interface Ops {
    void remove();

    default void doBeforeRemove() {}
}

class A implements Ops{
    @Override
    public void remove() {
        System.out.println("REMOVED A");
    }
}

class B implements Ops {
    @Override
    public void remove() {
        System.out.println("REMOVED B");
    }

    @Override
    public void doBeforeRemove() {
        System.out.println("SOMETHING TO DO BEFORE REMOVE");
    }
}

public class Proba2 {
    public static void main(String[] args) {
        List<Ops> ops = List.of(new A(), new B());
        for(Ops o : ops){
            br.doBeforeRemove();
            o.remove();
        }
    }
}

【讨论】:

  • 我不喜欢客户端负责知道应该在 remove 之前调用 doBeforeRemove
  • 没错,没有任何上下文,doBeforeRemove() 似乎不应该是一个 API。
【解决方案2】:

如上一个答案所述,这个例子并不违反里氏替换原则。

但满足 LSP 并不是 OOP 不鼓励类型检查的原因,例如 instanceof。 OOP 最强大的特性是多态性,而类型检查是多态性的对立面。依赖 instanceof 表明 API 并非旨在利用 OOP。您可以重新设计 API 以支持 OOP,但您也可以考虑较少关注多态性的替代编程范式。

总之,LSP 不是 OOP 的主要目标。

【讨论】:

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