依赖反演原则说(Head First Java(:
- 依赖于抽象。不要依赖具体的类。
关于继承意味着什么?作为子类依赖于具体的类。
我问一个案例,比如说 - 有一个接口Bird
(没有飞行方法,因为有些鸟不能飞(,它代表所有非飞鸟。所以我创建了一个类 -NonFlyingBird
实现Bird
.
现在我想为会飞的鸟做一个课程。由于NonFlyingBirds
和FlyingBirds
具有相同的属性,我将FlyingBirds
从NonFlyingBirds
和实现Flyable
扩展到赋予其飞行行为。
它是否违反了依赖反转原则,因为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
+ 飞行行为相同的属性和方法。因此,通过继承重用代码是有意义的。
尽管我喜欢那里的答案中的策略示例,但我也会回答,因为我认为您对依赖反转原理有点困惑。
依赖反转原理意味着
不要使用它,如果你真的不需要 LinkedList 行为:
public class A {
private LinkedList<SomeClass> list;
//...
}
请改用它:
public class A {
private List<SomeClass> list; //or even use Collection or Iterable
//...
}
依赖关系是我们在类中使用的。
遗产
继承就是我们所说的IS-A关系,它与原则无关。如果要使A类继承B类,则需要回答一个问题:A是B是真的吗?如果你问这个问题,你会发现表达"FlyingBird
是一个NonFlyingBird
"是一个随机数。
重用具有继承的代码
想想看:不是所有的鸟都能飞,也不是只有鸟(如苍蝇(能飞。 这可能会引导我们产生一个想法,即我们应该像您已经做的那样创建界面 Flyable。然后我们应该NonFlyingBird
重命名为SimpleBird
,因为如果某种生物是鸟,并不意味着它可以飞。最后,您将获得:
class FlyingBird extends SimpleBird implements Flyable {
void fly() {
...
}
...
}
希望它能有所帮助。
简短回答:不。
更长的答案:使用策略模式。
依赖于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
并传入所需鸟类类型的FlightStrategy
。
是的,你的直觉是正确的 - 继承在子级和它的父级之间引入了不可破坏的编译时和运行时依赖关系。
因此,继承的应用是有限的:您应该只使用继承来实现"是"关系,而不是"具有要共享的代码"关系。不过,你应该小心:只有当一个对象在整个生命周期中"是"它的子类时,才继承。人类可以安全地扩展哺乳动物,但将学生定义为人类的子类是有风险的。人类可以不再是学生,你不能在运行时改变这一点。
class FlyingBird extends NonFlyingBird
这是异端邪说!:)
反对像"AbstractBird"这样的模板类的运动很强烈。不幸的是,许多入门编程书籍都教授这种代码共享模式。它本身并没有什么问题,但现在有更好的解决方案——战略、桥梁。在Java中,你甚至可以拥有穷人的特征 - 接口中的默认实现。
在我看来,当应用于继承时,依赖倒置原则转化为"有利于组合而不是继承"。