LSP SOLID
#программирование

SOLID. Что значит L

Настало время поговорить о том, что значит L в аббревиатуре SOLID. В качестве сегодняшнего примера возьмем простейшую ситуацию. Представьте, что вы идете на совещание (или на лекцию, семинар), и вам нужна с собой тетрадь, чтобы делать заметки. Вы берете тетрадь в клетку и внезапно начинаете ловить на себе негодующие взгляды. Как оказалось, на этом конкретном совещании (лекции, семинаре) принято писать в тетрадях в линейку. Окей, через пару часов вы идете на другое совещание и берете с собой тетрадь в линейку, но, по закону подлости, здесь уже приняты блокноты формата А4 без разметки. Вы снова чувствуете себя глупо. Казалось бы, какая разница, какая у вас тетрадь – в клетку, в линейку или блокнот без разметки – ведь нужна просто возможность делать заметки? Собственно, примерно об этом и говорит принцип подстановки Барбары Лисков, он же LSP.

L значит LSP

Не LSD, а LSP. Что расшифровывается как Liskov substitution principle — принцип подстановки Барбары Лисков. Да, Лисков – это не мужик. 

Сама Лисков сформулировала свой принцип следующим образом:

Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

«Ничего не понятно, но очень интересно». Можете почитать об этом на википедии, а я тем временем расскажу про LSP на нормальном человеческом языке.

Тетрадь в клетку или в линейку?

Вы пришли на первое совещание, имея инстанс тетради в клетку:

public class SquaredNotebook {
  public void writeNote(String note) {
    // Писать по одной букве на клетку, не выходить за пределы клетки
  }
}

В то время как у всех остальных были тетради в линейку:

public class LinedNotebook {
  public void writeNote(String note) {
    // Писать на каждой линии и не косить
  }
}
В чем проблема

Проблема в том, что на конкретном совещании почему-то ожидалось, что у всех будут именно тетради в линейку:

public class Meeting {
  private final List<LinedNotebook> notebooks;

  public Meeting(List<LinedNotebook> notebooks) {
    this.notebooks = notebooks;
  }

  public void speakImportantInfo(String info) {
    notebooks.foreach(notebook -> notebook.writeNote(info));
  }
}

Имея тетрадь класса SquaredNotebook, то есть тетрадь в клетку, вы не сможете принять участие в этом совещании. Просто потому, что вы не сможете запихнуть объект класса SquaredNotebook в коллекцию, параметризованную классом LinedNotebok.

Аналогичная история происходит и на втором митинге, где у всех блокноты без разметки:

public class Meeting2 {
  private final List<LayoutFreeNotebook> notebooks;

  public Meeting(List<LayoutFreeNotebook> notebooks) {
    this.notebooks = notebooks;
  }

  public void speakImportantInfo(String info) {
    notebooks.foreach(notebook -> notebook.writeNote(info));
  }
}

И наш класс блокнота без разметки выглядит следующим образом:

public class LayoutFreeNotebook {
  public void writeNote(String note) {
    // Писать как угодно, никаких клеток или линий
  }
}

Опять же, вы не сможете засунуть тетрадь в линейку в список блокнотов без разметки:

List<LayoutFreeNotebook> notebooks = new ArrayList<>();
LayoutFreeNotebook layoutFreeNotebook = new LayoutFreeNotebook();
LinedNotebook linedNotebook = new LinedNotebook();
notebooks.add(layoutFreeNotebook); // OK
notebooks.add(linedNotebook); // ОШИБКА. Ожидается объект класса LayoutFreeNotebook

Вы можете использовать var вместо явного указания типа, но ошибка будет такой же, потому что тип объекта вычисляется по правой части выражения:

var notebooks = new ArrayList<LayoutFreeNotebook>();
var layoutFreeNotebook = new LayoutFreeNotebook();
var linedNotebook = new LinedNotebook();
notebooks.add(layoutFreeNotebook); // OK
notebooks.add(linedNotebook); // ОШИБКА. Ожидается объект класса LayoutFreeNotebook
Применим LSP

Давайте теперь посмотрим, как LSP сможет нам помочь в этой нелегкой ситуации. Думаю, вы уже догадались, что нормальный митинг должен выглядеть как-то так:

public class Meeting {
  private final List<Notebook> notebooks;

  public Meeting(List<Notebook> notebooks) {
    this.notebooks = notebooks;
  }

  public void speakImportantInfo(String info) {
    notebooks.foreach(notebook -> notebook.writeNote(info));
  }
}

Здесь Notebook – это просто интерфейс, который декларирует один-единственный метод:

public interface Notebook {
  void writeNote(String note);
}

Каждая тетрадь должна реализовывать этот интерфейс:

public class SquaredNotebook implements Notebook {
  @Override
  public void writeNote(String note) {
    // Писать по одной букве на клетку, не выходить за пределы клетки
  }
}

public class LinedNotebook implements Notebook {
  @Override
  public void writeNote(String note) {
    // Писать на каждой линии и не косить
  }
}

public class LayoutFreeNotebook implements Notebook {
  @Override
  public void writeNote(String note) {
    // Писать как угодно, никаких клеток или линий
  }
}
Что стало лучше

Применив LSP (а не LSD), мы сделали следующий код абсолютно рабочим:

List<Notebook> notebooks = new ArrayList<>();
Notebook squaredNotebook = new SquaredNotebook();
LinedNotebook linedNotebook = new LinedNotebook();
LayoutFreeNotebook layoutFreeNotebook = new LayoutFreeNotebook();
notebooks.add(squaredNotebook); // OK
notebooks.add(linedNotebook); // OK
notebooks.add(layoutFreeNotebook); // OK

С var тоже все будет хорошо:

var notebooks = new ArrayList<Notebook>();
var squaredNotebook = new SquaredNotebook();
var linedNotebook = new LinedNotebook();
var layoutFreeNotebook = new LayoutFreeNotebook();
notebooks.add(squaredNotebook); // OK
notebooks.add(linedNotebook); // OK
notebooks.add(layoutFreeNotebook); // OK

И митинг здоровых людей, где каждый может писать в любимой тетради, выглядит вот так (еще раз):

public class Meeting {
  private final List<Notebook> notebooks;

  public Meeting(List<Notebook> notebooks) {
    this.notebooks = notebooks;
  }

  public void speakImportantInfo(String info) {
    notebooks.foreach(notebook -> notebook.writeNote(info));
  }
}
Вывод

Не выгоняйте с совещаний людей, которые пишут в тетрадях в клетку. Пусть они сядут рядом с теми, кто пишет в тетрадях в линейку. Не нарушайте LSP и живите дружно.

Предыдущие статьи из цикла SOLID:
S: https://baddev.ru/solid-srp/
O: https://baddev.ru/solid-ocp/

Понравилось? Подписывайтесь на меня в соцсетях!

 
Facebook
Twitter
VK
guest
0 Комментариев
Inline Feedbacks
View all comments
Social media & sharing icons powered by UltimatelySocial
0
Нравится? Оставьте комментарий!x
()
x