我的情况与Code Complete中提到的Steve McConnell's非常相似。只有我的问题是基于车辆和三轮车才恰好依据法律属于汽车类别。到目前为止,汽车有四个轮子。无论如何,我的域名都不必要地复杂,因此很容易坚持使用下面的猫。
要怀疑那些覆盖例程而在里面什么都不做的类 派生例程这通常表示设计中的错误 基类。例如,假设你有一个类Cat和一个 例行Scratch()并假设你最终发现了一些 猫是声明的,不能划伤。你可能想要创造一个 派生自Cat的ScratchlessCat并重写Scratch() 例行公事无所事事。这种方法存在几个问题:
它违反了Cat中提出的抽象(接口契约) class通过改变其接口的语义。
当您将其扩展到其他方法时,此方法很快就会失控 派生类。当你找到一只没有尾巴的猫时会发生什么?或者a 猫不会抓老鼠?还是一只不喝牛奶的猫? 最终你会得到类似的派生类 ScratchlessTaillessMicelessMilklessCat。
随着时间的推移,这种方法会产生令人困惑的代码 维护因为祖先类的接口和行为 暗示他们的后代的行为很少或根本没有。
解决此问题的地方不在基类中,而是在基类中 原来的猫类。创建一个Claws类并在其中包含它 猫类。根本问题是假设所有猫都划伤, 所以在源头解决这个问题,而不是仅仅将其包扎起来 目的地。
根据他上面的伟大着作中的文字。以下是不好的
父类不必是抽象的
public abstract class Cat {
public void scratch() {
System.out.println("I can scratch");
}
}
派生类
public class ScratchlessCat extends Cat {
@Override
public void scratch() {
// do nothing
}
}
现在他建议创建另一个类Claws
,但我不明白如何使用此类来避免需要ScratchlessCat#Scratch
。
答案 0 :(得分:12)
并非所有的猫都有爪子并且能够抓挠是一个很大的线索,Cat
不应该在其API中定义公共scratch
方法。第一步是考虑您首先定义scratch
的原因。也许猫在受到攻击时会刮伤;如果不是他们嘶嘶声或逃跑。
public class Cat extends Animal {
private Claws claws;
public void onAttacked(Animal attacker) {
if (claws != null) {
claws.scratch(attacker);
}
else {
// Assumes all cats can hiss.
// This may also be untrue and you need Voicebox.
// Rinse and repeat.
hiss();
}
}
}
现在您可以将任何Cat
子类替换为另一个子类,它将根据是否具有爪子而正确运行。您可以定义DefenseMechanism
类来组织各种防御,例如Scratch
,Hiss
,Bite
等。
答案 1 :(得分:9)
您仍然可以使用scratch()
方法,但派生类不会覆盖它:
public class Cat {
Claw claw_;
public Cat(Claw claw) {claw = claw_;}
public final void scratch() {
if (claw_ != null) {
claw_.scratch(this);
}
}
}
这允许您将抓取逻辑委派给包含的Claw
对象(如果存在)(如果没有抓爪则不会刮擦)。从cat派生的类在如何划分的问题上没有发言权,因此不需要根据能力创建阴影层次结构。
此外,因为派生类不能更改方法实现,所以它们没有打破基类接口中scratch()
方法的预期语义的问题。
如果你把它带到极端,你可能会发现你有很多类而且没有多少派生 - 大多数逻辑被委托给组合对象,而不是委托给派生类。