Поэтому, даже при том, что высший приоритет имеет защита
Заключение
Принцип открытости/закрытости – одна из движущих сил в архитектуре систем. Его цель – сделать систему легко расширяемой и обезопасить ее от влияния изменений. Эта цель достигается делением системы на компоненты и упорядочением их зависимостей в иерархию, защищающую компоненты уровнем выше от изменений в компонентах уровнем ниже.
9. Принцип подстановки Барбары Лисков
В 1988 году Барбара Лисков написала следующие строки с формулировкой определения подтипов.
Чтобы понять эту идею, известную как принцип подстановки Барбары Лисков (Liskov Substitution Principle; LSP), рассмотрим несколько примеров.
Руководство по использованию наследования
Представьте, что у нас есть класс с именем License
calcFee
, который вызывается приложением Billing
. Существует два «подтипа» класса License:
PersonalLicense
и BusinessLicense
. Они реализуют разные алгоритмы расчета лицензионных отчислений.Рис. 9.1.
Класс License и его производные, соответствующие принципу LSPЭтот дизайн соответствует принципу подстановки Барбары Лисков, потому что поведение приложения Billing
License
.Проблема квадрат/прямоугольник
Классическим примером нарушения принципа подстановки Барбары Лисков может служить известная проблема квадрат/прямоугольник (рис. 9.2).
Рис. 9.2.
Известная проблема квадрат/прямоугольникВ этом примере класс Square
Rectangle
(представляющего прямоугольник), потому что высоту и ширину прямоугольника можно изменять независимо; а высоту и ширину квадрата можно изменять только вместе. Поскольку класс User
полагает, что взаимодействует с экземпляром Rectangle
, его легко можно ввести в заблуждение, как демонстрирует следующий код:Rectangle r =…
r. setW(5);
r. setH(2);
assert(r.area == 10);
Если на место… подставить код, создающий экземпляр Square
assert
потерпит неудачу. Единственный способ противостоять такому виду нарушений принципа LSP – добавить в класс User
механизм (например, инструкцию if), определяющий ситуацию, когда прямоугольник фактически является квадратом. Так как поведение User
зависит от используемых типов, эти типы не являются заменяемыми (совместимыми).LSP и архитектура
На заре объектно-ориентированной революции принцип LSP рассматривался как руководство по использованию наследования, как было показано в предыдущих разделах. Но со временем LSP был преобразован в более широкий принцип проектирования программного обеспечения, который распространяется также на интерфейсы и реализации.
Подразумеваемые интерфейсы могут иметь множество форм. Это могут быть интерфейсы в стиле Java, реализуемые несколькими классами. Или это может быть группа классов на языке Ruby, реализующих методы с одинаковыми сигнатурами. Или это может быть набор служб, соответствующих общему интерфейсу REST.
Во всех этих и многих других ситуациях применим принцип LSP, потому что существуют пользователи, зависящие от четкого определения интерфейсов и замещаемости их реализаций.
Лучший способ понять значение LSP с архитектурной точки зрения – посмотреть, что случится с архитектурой системы при нарушении принципа.
Пример нарушения LSP
Допустим, что мы взялись за создание приложения, объединяющего несколько служб, предоставляющих услуги такси. Клиенты, как предполагается, будут использовать наш веб-сайт для поиска подходящего такси, независимо от принадлежности к той или иной компании. Как только клиент подтверждает заказ, наша система передает его выбранному такси, используя REST-службу.
Теперь предположим, что URI службы является частью информации, хранящейся в базе данных водителей. Выбрав водителя, подходящего для клиента, наша система извлекает URI из записи с информацией о водителе и использует ее для передачи заказа этому водителю.