Принцип подстановки Лискова VS Принцип разделения интерфейса

У меня проблемы с пониманием этих двух принципов. Это немного давно читаемый вопрос, так что наберитесь терпения.

Предположим, что у нас есть класс

abstract class Shape {
    abstract void onDraw();
}

и интерфейс

interface SideCountable {
  int getSidesCount();
}

Затем мы создаем два дочерних класса

class Quad extends Shape {

   @Override
   public void onDraw() {
   //some draw logic here
   }
}

class Triangle extends Shape {

   @Override
   public void onDraw() {
   //some draw logic here
   }
}

А теперь сделаем Shape орудия SideCountable

abstract class Shape implements SideCountable{
    abstract void onDraw();
}

и вносить изменения в детские классы

class Quad extends Shape {

   @Override
   public int getSidesCount() {
       return 4;
   }

   @Override
   public void onDraw() {
   //some draw logic here
   }
}

class Triangle extends Shape {

   public int getSidesCount() {
       return 3;
   }

   public void onDraw() {
   //some draw logic here
   }
}

И создадим тестовую функцию для этой структуры (следуя LSP):

public void printSidesCount(List<Shape> shapes) {
    for(int i=0; i < shapes.size(); i++) {
        System.out.println(shapes.get(i).getSidesCount());
    }
}

И здесь я хочу остановиться, потому что на самом деле на следующем этапе я застрял. Что, если мы создадим третий классCircle?

class Circle extends Shape {

   public int getSidesCount() {
       return ???;
   }

   @Override
   public void onDraw() {
   //some draw logic here
   }
}

Круг не имеет сторон, поэтому реализация SideCountableдля детей это звучит смешно. Хорошо, мы можем перенести реализацию только на Quad и Triangle, но в этом случае LSP больше не будет работать. Может ли кто-нибудь описать, что мне лучше всего делать?

  • Покидать SideCountable в Shape class и вернуть 0 для Circle и нарушить принцип разделения интерфейса?
  • Переехать SideCountable к Quad а также Triangle и нарушить принцип LSP?

1 ответ

Решение

Прежде всего, ваш метод printSidesCount нужно только знать, что список содержит SideCountableобъекты. Таким образом, задав его параметру типList<Shape>является более конкретным, чем необходимо. Дайте емуList<SideCountable> вместо:

public void printSidesCount(List<SideCountable> sideCountables) {
    for(int i=0; i < (); i++) {
        System.out.println(sideCountables.get(i).getSidesCount());
    }
}

Или даже List<? extends SideCountable> что означает "список произвольного неизвестного типа, который реализует SideCountable":

public void printSidesCount(List<? extends SideCountable> sideCountables) {
    for(int i=0; i < sideCountables.size(); i++) {
        System.out.println(sideCountables.get(i).getSidesCount());
    }
}

Если не все формы имеют счетное количество сторон, тогда класс Shape не должен реализовывать интерфейс SideCountable. Вместо этого сделайте классыQuad а также Triangle реализовать интерфейс SideCountable помимо расширения класса Shape:

class Quad extends Shape implements SideCountable {
    // ...
}

class Triangle extends Shape implements SideCountable {
    // ...
}

И сделать класс Circle расширять Shape но не реализовать SideCountable:

class Circle extends Shape { // Not SideCountable
    // ...
}

Когда вы сделаете это так, система типов поможет вам:

  • вы можете пройти List<Quad> или List<Triangle> к printSidesCount поскольку эти типы реализуют SideCountable
  • ты не можешь пройти List<Circle> к printSidesCount, что хорошо, потому что пытаться сделать это не имеет смысла для списка кругов
Другие вопросы по тегам