Принцип підстановки Лісков
Принцип заміщення Лісков (англ. Liskov Substitution Principle, LSP) в об'єктно-орієнтованому програмуванні — це спеціальне визначення підтипу, запропоноване Барбарою Лісков у 1987 році на конференції у доповіді під назвою "Абстракція даних та ієрархія"[1]. У своїй статті [2] Лісков сформулювала свій принцип так:
Таким чином ідея Лісков про «підтип» визначає поняття заміщення — якщо S підтип T, тоді об'єкти типу T в програмі можуть бути заміщені об'єктами типу S без будь-яких змін бажаних властивостей цієї програми (наприклад, коректність[en]). Цей принцип — найважливіший критерій для оцінки якості ухвалених рішень при побудові ієрархій успадкування. Сформулювати його можна у вигляді простого правила: тип S буде підтипом Т тоді і тільки тоді, коли кожному об'єктові o1 типу S відповідає певний об'єкт o2 типу T таким чином, що для всіх програм P, реалізованих в термінах T, поведінка P не зміниться, якщо o2 замінити на o1. Принцип Лісков є суворішим за поняття підтипу в теорії типів, яка вимагає тільки:
Див. коваріантність і контраваріантність і типи даних. Проєктування за контрактомПринцип заміщення Лісков близько стосується методології проєктування за контрактом, і веде до деяких обмежень на те, як контракти можуть взаємодіяти з успадкуванням:
Функція, що обробляє ієрархію класів з порушеннями принципу Лісков, використовує посилання на базовий клас, але також вимушена мати інформацію про підклас. Така функція також порушує принцип відкритості/закритості оскільки її необхідно змінювати в разі появи нових похідних класів. У цьому контексті принцип заміщення Лісков можна переформулювати так:
Андрей Александреску та Герб Саттер в книзі «C++ Coding Standards» описують виконання цього принципу так:
На думку авторів, публічне успадкування можна використовувати тільки коли виконується принцип LSP. Приватне успадкування дозволене тільки для доступу до protected частини та заміщення віртуальних методів. Для всіх інших випадків, успадковування використовувати не бажано. ПрикладРоберт Мартін зазначає, що до перших ознак порушення принципу підстановки Лісков є наявність функцій, які перевіряють тип отриманого аргументу для визначення своєї поведінки:[3] void DrawShape(const Shape& s)
{
if (typeid(s) == typeid(Square))
DrawSquare(static_cast<Square&>(s));
else if (typeid(s) == typeid(Circle))
DrawCircle(static_cast<Circle&>(s));
}
Таке може трапитись коли успадковані класи порушують контракт базового класу. Наприклад, квадрат можна представити як підклас прямокутника, оскільки квадрат це і є прямокутник з рівними сторонами (інший наведений ним приклад коло — вироджений випадок еліпсу)[3]. Однак, оскільки квадрат успадкує від прямокутника обидва атрибута — ширину і висоту, то постане проблема: як гарантувати їхню рівність в методах присвоєння (так званих «сеттерах»)? Просте рішення може мати такий вигляд: void Square::SetWidth(double w)
{
Rectangle::SetWidth(w);
Rectangle::SetHeight(w);
}
void Square::SetHeight(double h)
{
Rectangle::SetHeight(h);
Rectangle::SetWidth(h);
}
Але такий код порушує принцип підстановки Лісков для тих функцій, які покладаються на збереження переданих значень: void g(Rectangle& r)
{
r.SetWidth(5);
r.SetHeight(4);
assert(r.GetWidth() * r.GetHeight()) == 20);
}
Таким чином, порушення принципу підстановки Лісков може спричиняти порушення принципу відкритості/закритості[4]. Примітки
Посилання |