15

我的情况与Steve McConnell在 Code Complete 中提到的情况非常相似。只是我的问题是基于 Vehicles 的,而 Trike 恰好在法律上属于 Cars 类别。到目前为止,汽车有四个轮子。无论如何,我的域都是不必要的复杂,所以很容易坚持下面的猫示例。

对覆盖例程并且在派生例程内不执行任何操作的类保持怀疑 这通常表明基类的设计存在错误。例如,假设您有一个 Cat 类和一个例程 Scratch(),并假设您最终发现一些猫已经去爪并且不能抓挠。您可能很想创建一个从 Cat 派生的名为 ScratchlessCat 的类,并重写 Scratch() 例程以不做任何事情。这种方法存在几个问题:

它通过更改其接口的语义违反了 Cat 类中呈现的抽象(接口契约)。

当您将这种方法扩展到其他派生类时,这种方法很快就会失控。当你发现一只没有尾巴的猫时会发生什么?还是一只不会抓老鼠的猫?还是一只不喝牛奶的猫?最终你会得到像 ScratchlessTaillessMicelessMilklessCat 这样的派生类。

随着时间的推移,这种方法会产生难以维护的代码,因为祖先类的接口和行为对其后代的行为几乎没有暗示。

解决这个问题的地方不在基类中,而是在原始的 Cat 类中。创建一个 Claws 类并将其包含在 Cats 类中。根本问题是假设所有的猫都会抓挠,所以从源头解决这个问题,而不是在目的地包扎它。

根据上面他伟大著作的文字。跟风不好

父类不必是抽象的

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.

4

2 回答 2

11

并非所有的猫都有爪子并且能够抓挠是一个重要线索,Cat不应scratch在其 API 中定义公共方法。第一步是考虑你为什么首先定义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类来组织各种防御,例如ScratchHissBite等。

于 2012-05-28T01:12:39.543 回答
10

您仍然有一个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()基类接口中方法的预期语义。

如果你把它推到极端,你可能会发现你有很多类而没有很多派生——大多数逻辑都委托给组合对象,而不是委托给派生类。

于 2012-05-28T01:12:05.673 回答