2012-05-28 7 views
13

Я в ситуации, очень похожей на то, что Steve McConnell's в Code Complete упомянули. Только в том, что моя проблема основана на транспортных средствах и Trike, оказывается, по закону попадает в категорию автомобилей. До сих пор у автомобилей было четыре колеса. В любом случае мой домен излишне сложный, поэтому его легко придерживаться примерами кошек ниже.Как я могу избежать нарушения Принципа замещения Лискова (LSP)?

с подозрением к классам, которые переопределяют процедуру и не делать ничего внутри производный рутина Это обычно указывает на ошибку в конструкции базового класса. Например, предположим, что у вас есть класс 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.

+2

Пример Cat является хорошей иллюстрацией 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 не должен определять общедоступный метод 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 организовать различные защитные, такие как Scratch, Hiss, Bite и т.д.