Принцип подстановки [Барбары] Лисков (Liskov Substitution Principle — LSP, буква L в аббревиатуре SOLID), сформулирован Барбарой Лисков в 1987 году и звучит следующим образом:
Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.
Упрощенное описание этого принципа предложил Роберт Мартин:
Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом.
Иными словами, поведение реализующих и наследующих классов не должно противоречить поведению базовых типов.
Практическое применение принципа
В посте «SOLID на практике — принцип инверсии зависимостей» фигурировал интерфейс TopicRepository и реализующий его класс JpaEntityManagerTopicRepository:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public interface TopicRepository { Optional<Topic> findOneById(int id); Topic save(Topic topic); } public class JpaEntityManagerTopicRepository implements TopicRepository { private final EntityManager entityManager; @Override public Optional<Topic> findOneById(int id) { return Optional.ofNullable(this.entityManager.find(Topic.class, id)); } @Override @Transactional public Topic save(Topic topic) { return this.entityManager.merge(topic); } } |
Следуя принципу подстановки Барбары Лисков, мы можем разработать реализацию интерфейса TopicRepository с применением, например, репозиториев Spring Data, которая будет иметь аналогичное поведение.
Определим интерфейс Spring Data:
1 2 |
public interface TopicCrudRepository extends CrudRepository<Topic, String> { } |
и напишем реализацию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class SpringDataTopicRepository implements TopicRepository { private final SpringDataTopicRepository repository; @Override public Optional<Topic> findOneById(String id) { return this.repository.findOneById(id); } @Override public Topic save(Topic topic) { return this.repository.save(topic); } } |
Объект типа SpringDataTopicRepository можно использовать в качестве реализации TopicRepository вместо JpaEntityManagerTopicRepository, поведение приложения от этого не изменится. Если изменится источник данных, и вместо PostgreSQL, например, будет использоваться MongoDB, то достаточно будет написать реализацию TopicRepository, использующую MongoDB. Впрочем, если в качестве базового типа репозиториев Spring Data используется CrudRepository, то и этого делать не придётся — Spring Data сделает всё за разработчика.
Более того, пока функциональность TopicRepository может быть реализована средствами Spring Data, мы можем не писать реализацию этого интерфейса, а наследовать интерфейс SpringDataTopicRepository от TopicRepository и CrudRepository, что не противоречит принципам SOLID, но позволяет уменьшить количество кода:
1 2 |
public interface SpringDataTopicRepository extends CrudRepository<Topic, String>, TopicRepository { } |