Подтвердить что ты не робот

Как я могу избежать нарушения Принципа замещения Лискова (LSP)?

Я в ситуации, очень похожей на то, о чем говорил

Будьте подозрительны к классам, которые переопределяют процедуру и ничего не делают внутри производная процедура Это обычно указывает на ошибку в дизайне базовый класс. Например, предположим, что у вас есть класс Cat и рутинной Scratch() и предположим, что вы в конце концов узнаете, что некоторые кошки объявляются и не могут поцарапать. Возможно, у вас возникнет соблазн создать класс, полученный от Cat с именем ScratchlessCat и переопределяющий Scratch() рутину ничего не делать. Этот подход представляет несколько проблем:

Он нарушает абстракцию (контракт интерфейса), представленный в Cat класса, изменив семантику интерфейса.

Этот подход быстро выходит из-под контроля, когда вы распространяете его на другие производные классы. Что происходит, когда вы найдете кошку без хвоста? Или кот, который не ловит мышей? Или кот, который не пьет молоко? В конце концов вы получите производные классы, такие как ScratchlessTaillessMicelessMilklessCat.

С течением времени этот подход порождает код, который запутывает поддерживать, потому что интерфейсы и поведение классов предков мало или совсем ничего не говорят о поведении их потомков.

Место для исправления этой проблемы находится не в базовом классе, а в оригинальный Cat класс. Создайте класс 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.

4b9b3361

Ответ 1

У вас все равно будет метод scratch(), но он не будет переопределен производными классами:

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

Это позволяет вам делегировать логику царапин содержащемуся объекту Claw, если он присутствует (и не царапать, если нет когтей). Классы, полученные от кошки, не имеют права говорить о том, как поцарапать, поэтому нет необходимости создавать теневые иерархии, основанные на способностях.

Кроме того, поскольку производные классы не могут изменить реализацию метода, нет проблемы с тем, что они нарушают предполагаемую семантику метода scratch() в интерфейсе базового класса.

Если вы примете это до крайности, вы можете обнаружить, что у вас много классов и не так много выводов - большинство логических функций делегируются объектам композиции, а не доверяются производным классам.

Ответ 2

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