Java 继承是否与依赖倒置原则相矛盾

Java 继承是否与依赖倒置原则相矛盾,java,oop,design-patterns,dependency-inversion,Java,Oop,Design Patterns,Dependency Inversion,依赖项反转原则说(头先Java): 依赖抽象。不要依赖于具体的类别 它对于继承意味着什么?作为子类依赖于具体类 我在问一个例子,比如说-有一个接口Bird(没有fly方法,因为有些鸟不能飞),它代表所有不飞的鸟。所以我创建了一个类-NonFlyingBird,它实现了Bird 现在我想为会飞的鸟上一节课。由于NonFlyingBirds和FlyingBirds具有相同的属性,我从NonFlyingBirds扩展了FlyingBirds,并实现了FlyingBirds,赋予其飞行行为 当Fly

依赖项反转原则说(头先Java):

  • 依赖抽象。不要依赖于具体的类别
它对于继承意味着什么?作为子类依赖于具体类

我在问一个例子,比如说-有一个接口
Bird
(没有fly方法,因为有些鸟不能飞),它代表所有不飞的鸟。所以我创建了一个类-
NonFlyingBird
,它实现了
Bird

现在我想为会飞的鸟上一节课。由于
NonFlyingBirds
FlyingBirds
具有相同的属性,我从
NonFlyingBirds
扩展了
FlyingBirds
,并实现了
FlyingBirds
,赋予其飞行行为

FlyingBirds
从一个具体的类
NonFlyingBirds
扩展时,它是否打破了依赖倒置原则

interface Bird {   // Represents non Flying birds

    void getColor();
    void getHeight();
    ...
 }



class NonFlyingBird implements Bird {
     void getColor();
     void getHeight();
      ...
  }


class FlyingBird extends NonFlyingBird implements Flyable { // Does it break Dependency Inversion principle by extending concrete NonFlyingBird class? 

    Flyable fly;
    ...
  }
注意-我扩展的唯一原因是
FlyingBird
具有与
NonFlyingBird
+飞行行为相同的属性和方法。因此,通过继承重用代码是有意义的

简短回答:不

更详细的回答:使用策略模式

依赖于
Bird
接口的类仍然可以得到
FlyingBird
NotFlyingBird
实例,而不知道它们之间的区别

Bird
的接口仍然相同,或者通常应该相同,如果类确实具有不同的接口(调用代码所依赖的
FlyingBird
中的新方法),则存在问题

也许解决问题的更好方法是使用策略模式

即兴示例:

public interface Bird {

    void fly();
}

public class BirdImpl implements Bird {

    private FlightStrategy flightStrategy;

    public BirdImpl(FlightStrategy flightStrategy) {

        this.flightStrategy = flightStrategy;
    }

    public void fly() {

        this.flightStrategy.fly();
    }
}

public interface FlightStrategy {

    void fly();
}

public class FlyingBirdFlightStrategy implements FlightStrategy {

    public void fly() {

        System.out.println("Wings flap");
        System.out.println("Wings flap");
        System.out.println("Wings flap");
        System.out.println("Wings flap");
    }
}

public class NonFlyingBirdFlightStrategy implements FlightStrategy {

    public void fly() {

        // do nothing non flying birds can't fly.
    }
}

然后,在创建要使用的
Bird
时,创建默认的
BirdImpl
,并为所需的Bird类型传入
FlightStrategy

是的,您的直觉是正确的-继承在子级和父级之间引入了不可中断的编译时和运行时依赖关系

因此继承的应用是有限的:您应该只使用继承来实现“is”关系,而不是“has-code-to-shared”关系。但是,您应该小心:仅当对象在整个生命周期中“是”其子类时才继承。人类可以安全地扩展哺乳动物,但将学生定义为人类的一个子类是有风险的。人类可以不再是学生,你不能在运行时改变这一点

class FlyingBird extends NonFlyingBird
这是异端邪说!:)

有一种强烈的运动反对像“AbstractBird”这样的模板类。不幸的是,许多介绍性编程的书籍都教授这种代码共享模式。它本身并没有什么问题,但现在有了更好的解决方案——战略、桥梁。在Java中,您甚至可以拥有穷人的特征——接口中的默认实现


在我看来,依赖倒置原则,当应用于继承时,转化为“优先组合而非继承”。

尽管我喜欢答案中的策略示例,但我也会回答,因为我认为您对依赖倒置原则有点困惑

依赖倒置原理 如果您真的不需要LinkedList行为,请不要使用此选项:

public class A {
    private LinkedList<SomeClass> list;
//...
}

希望能有帮助。

那些不会飞的鸟呢?它们在这个设计中是如何表现的?好的,很好的观点。我猜音乐语录不是很好的例子。。。我会简化的。但基本上,一只不会飞的鸟的飞行方法,也不会起任何作用。一只不会飞的鸟不会飞。使用空的方法有什么好处吗?我在某个地方读到,这是我需要重构的信号。这就是我提出的架构(继承)的全部要点。嗯,这要看情况而定。在我看来,空的、没有行为的方法没有任何内在的错误。在这种情况下,我觉得这是正确的行为。我认为
fly()
是一种非常合理的方法,您可以在名为
Bird
的接口上找到它。我认为,当你把一只不会飞的鸟捡起来扔向空中(当然是轻轻地)时,正确的行为是不要飞。现在,每一只
会飞的鸟
也是一只
不会飞的鸟
,你违反了@johanneskun的规定,但我不认为用
非飞鸟
替换
飞鸟
会有什么问题?也许现在不会,但下个月下一个实习生会说:“我知道,我只是用
instanceof
检查它是非飞鸟还是飞鸟”,这会诅咒你的决定。@johanneskun,说得好。在我看来,我需要为移动和非移动的鸟类创建两个不同的类。这是正确的吗?你有一些很好的答案,下面,但问题是错误的。当你在继承方面做了一些糟糕的事情时,这不是继承的问题。问题是你用它做了什么。别这么做。你能解释一下,‘不是所有的鸟都会飞,也不是只有鸟会飞’?@lynxx,对不起,我写得很复杂。我更新了这篇文章。注意战略和桥梁(以及所有的GoF模式)已经有30多年的历史了,所以说“现在有更好的解决方案”有点奇怪。这些解决方案已经存在了几十年。不过,我完全同意继承是一种具体的依赖关系。看到我用“是a”做了什么吗?
public class A {
    private List<SomeClass> list; //or even use Collection or Iterable
//...
}
class FlyingBird extends SimpleBird implements Flyable { 

    void fly() {
        ...
    }
    ...
}