2012-05-28 76 views
13

我处于与Code Complete中提到的Steve McConnell's非常相似的情况。只有我的问题是基于车辆和三轮车碰巧在法律属于汽车类。到目前为止,汽车有四个车轮。任何方式我的域名是不必要的复杂,所以很容易坚持下面的猫的例子。如何避免违反Liskov替代原则(LSP)?

要怀疑重写常规做内 任何派生例行这通常表明在 基类设计中的错误类。例如,假设你有一个类Cat和一个例程Scratch(),并且假设你最终发现有一些 猫被声明并且不能从头开始。您可能会试图创建一个从名为ScratchlessCat的Cat派生的类 ,并覆盖Scratch() 例程以便不执行任何操作。这种方法存在几个问题:

它违反了抽象(接口契约)通过改变其接口的语义在猫 类呈现。

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

随着时间的推移,这种方法会产生令人困惑的代码,因为祖先类的接口和行为对其后代的行为意义不大或根本没有意义。

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

根据他上面的伟大的书的文字。以下是坏

父类并不一定是抽象

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

+2

猫的例子是LSP的一个很好的例证。在了解LSP时,我曾惊讶于SO和其他网站上有多少不好的例子以及有多少误解。 – SteveT

回答

8

你仍然有scratch()方法,但它不会被派生类中重写:

public class Cat { 
    Claw claw_; 
    public Cat(Claw claw) {claw = claw_;} 
    public final void scratch() { 
    if (claw_ != null) { 
     claw_.scratch(this); 
    } 
    } 
} 

这可以让你的抓挠逻辑委托给包含Claw对象,如果存在的话(而不是从头开始如果没有爪子)。来自猫的类在如何划伤问题上没有发言权,所以不需要根据能力创建影子层次结构。另外,因为派生类不能更改方法实现,所以它们不存在打破基类接口中方法scratch()的预期语义的问题。

如果您将此置于极端,您可能会发现您有很多类并且派生类没有多少 - 大多数逻辑委托给组合对象,而不委托给派生类。

11

并非所有的猫都有爪并且能够抓挠是一个很大的线索,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组织的各种防御如ScratchHissBite

相关问题