Стек, що розширює LinkedList. Порушення принципу заміни Ліскова?


13

Клас LinkedList існує з такими функціями, як add_first (), add_last (), add_after (), remove_first (), remove_last () та remove ()

Тепер існує клас Stack, який забезпечує такі функції, як push (), pop (), peek () або top (), і для реалізації цих методів він розширює методи класу LinkedList. Це порушення Принципу заміни Ліскова?

Наприклад, розглянемо випадок add_after (), щоб додати вузол на n-му місці пов'язаного списку. Це можна зробити в базовому класі, але не в класі Stack. Чи ослаблені постумови тут чи ви модифікуєте метод add_after (), щоб додати його до вершини стека?

Крім того, якщо це не порушення, це погана конструкція? І як би ви реалізували функцію Stack за допомогою класу LinkedList?


6
Питання Що було б недоліком у визначенні класу як підкласу самого списку? запитує про дещо іншу проблему, але більшість відповідей застосовуються і тут: вам краще створити стек зі списком як приватний член, а не успадковувати зі списку.
амон

7
Так, це погана конструкція, оскільки вона не дозволить вам змінити внутрішнє представлення стека на щось інше, ніж пов'язаний список (наприклад, масив). Ви також виставляєте операції, стеки яких зазвичай не підтримують. Використовуйте склад замість спадкування.
Лі

2
Ви хочете продовжити LinkedList? Можливо, ви захочете прислухатися до порад певного Джошуа Блоха (який відіграв головну роль у розробці рамки колекцій Java), коли він сказав: "Віддавайте перевагу композиції над спадщиною" - Можливо, ви маєте LinkedListвсередині свого класу стека, але насправді не розширюєте його?
corsiKa

1
Я б сказав, що він не може задовольнити принцип заміни Ліскова, оскільки "стек - це свого роду пов'язаний список" - це неправдиве твердження. "Стек" - це дійсно інтерфейс (я маю на увазі концептуально, а не конструкт Java). Стек може бути реалізований із пов'язаним списком. Як уже говорили інші, ви б використовували приватного члена даних типу, LinkedListякий робить важкий підйом за кадром (композиція). Таким чином, користувачі вашого коду не можуть випадково використовувати стек там, де їм потрібен Список, або навпаки.
Ясмійн

1
Здається, що було б безпечніше було б навпаки. Якби я робив це, у мене, ймовірно, був би клас пов'язаного списку, що реалізує інтерфейс "стек", оскільки він цілком здатний це робити. Інакше це неправильний шлях, оскільки стек - це концепція, пов'язаний список - це реалізація, яка може діяти як стек серед інших речей.
Vality

Відповіді:


31

Тепер існує клас Stack, який забезпечує такі функції, як push (), pop (), peek () або top (), і для реалізації цих методів він розширює методи класу LinkedList. Це порушення Принципу заміни Ліскова?

Ні. Цілком чудово додавати методи в підтип.

Наприклад, розглянемо випадок add_after (), щоб додати вузол на n-му місці пов'язаного списку. Це можна зробити в базовому класі, але не в класі Stack.

Це є порушенням LSP. LSP каже, що екземпляри підтипу повинні бути замінені на екземпляри супертипу без зміни бажаних властивостей програми. Якщо підтип видаляє метод, то будь-який код, який викликає цей метод, вийде з ладу (або отримає NoMethodErrorвиняток, або щось уздовж цих рядків). Зрозуміло, що "не збій" - бажана властивість.

Чи ослаблені постумови тут чи ви модифікуєте метод add_after (), щоб додати його до вершини стека?

Змінення add_after()методу таким способом - це порушення Правила історії (найважливіше з правил!), І, таким чином, не допомагає виправити порушення LSP.

І як би ви реалізували функцію Stack за допомогою класу LinkedList?

За допомогою складу.

ПРИМІТКА: Все, що я написав вище, стосується лише мов, які плутають підтипи та підкласи! LSP стосується підтипу , а не підкласифікації. У мові , яка не плутає два, це було б цілком прийнятно , щоб зробити Stackпідклас LinkedList, до тих пір , поки ви не зробити його підтип з LinkedList.


9
Що таке правило історії? Мені не вдалося знайти його в Інтернеті.
Західло

10
Під час маніпулювання об'єктом підтипу та спостереження за ним за допомогою методів супертипу повинно бути неможливо спостерігати історію, яку також не можна було б спостерігати з об’єктом супертипу. Це правило є тим, що робить LSP застосовно до нефункціональних мов. Усі інші речі були відомі задовго до цього. Правила до- і післяумови - це переформулювання правил спів- та протирівномірності для типів функцій. Правило історії робить все це застосовно до змінних даних. Без цього LSP був би корисний лише для чисто функціональних мов.
Йорг W Міттаг

2
Я думаю, що пояснення в статті Вікіпедії досить добре: wikipedia.org/wiki/Liskov_substitution_principle Але, як завжди, вам слід справді прочитати першоджерело.
Йорг W Міттаг

@ JörgWMittag: Хоча я вважаю за доцільне, щоб типи включали в свій договір гарантії про те, що "історія" буде чи не буде дотримуватися, я не думаю, що це повинно бути необхідним, щоб кожен супертип мав можливість виробляти кожну послідовність спостережень що може бути можливим на об'єкті підтипу - лише те, що документ супертипу передбачає можливість того, що споживачі супертипу можуть спостерігати такі послідовності.
supercat

1

Оскільки все вже вирішувалося Йоргом W Міттагом, я лише детально деталізую наступну частину:

Чи ослаблені постумови тут чи ви модифікуєте метод add_after (), щоб додати його до вершини стека?

В основному, коли виникає питання, чи порушує якась ієрархія LSP, це залежить від того, який контракт ви представляєте. Отже, який контракт add_afterмає? Якщо це звучить, як би шалено не було, як-от "Додати вузол на n-й позиції або вгорі", ніж у тебе все добре, умова пост виконується, LSP не порушується. Інакше це порушення LSP.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.