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/
Понравилось? Подписывайтесь на меня в соцсетях!