Чи Принцип заміщення Ліскова не сумісний із інтроспекцією чи набором качок?


11

Я правильно розумію, що Принцип заміщення Ліскова не можна спостерігати мовами, де об’єкти можуть перевіряти себе, як те, що зазвичай є у мовах, що набираються на качках?

Наприклад, в Ruby, якщо клас Bуспадковує від класу A, то для кожного об'єкта xз A, x.classзбирається повернутися A, але якщо xце об'єкт B, x.classне збирається повертатися A.

Ось заява LSP:

Нехай д (х) є властивістю доказово про об'єкти х типу Т . Тоді д (у) має бути доказовою для об'єктів , у типу S , де S є підтипом T .

Так у Рубі, наприклад,

class T; end
class S < T; end

порушують LSP у цій формі, про що свідчить властивість q (x) =x.class.name == 'T'


Доповнення. Якщо відповідь "так" (LSP несумісний із самоаналізом), то іншим моїм питанням буде: чи є якась модифікована "слабка" форма LSP, яка, можливо, може мати місце для динамічної мови, можливо, за якихось додаткових умов та лише з особливими типами з властивостей .


Оновлення. Для довідки, ось ще одна рецептура LSP, яку я знайшов в Інтернеті:

Функції, що використовують покажчики або посилання на базові класи, повинні мати можливість використовувати об'єкти похідних класів, не знаючи цього.

І ще:

Якщо S є оголошеним підтипом T, об'єкти типу S повинні поводитись так, як очікується, що вони поводяться як об'єкти типу T, якщо вони розглядаються як об'єкти типу T.

Останній зазначається:

Зауважте, що LSP стосується очікуваної поведінки об'єктів. Слідкувати за LSP можна лише в тому випадку, коли зрозуміло, якою є очікувана поведінка об'єктів.

Це, здається, слабше, ніж оригінальне, і його можна було б спостерігати, але я хотів би, щоб це було формалізовано, зокрема пояснив, хто вирішує, яка очікувана поведінка.

Тоді LSP - це не властивість пари класів у мові програмування, а пари класів разом із заданим набором властивостей, задоволених класом предків? Практично, чи означає це, що для побудови підкласу (класу нащадків), що поважає LSP, слід знати всі можливі можливості використання класу предків? Згідно з LSP, клас предків повинен бути замінений будь-яким класом нащадків, правда?


Оновлення. Я вже прийняв відповідь, але я хотів би додати ще один конкретний приклад від Рубі, щоб проілюструвати питання. У Ruby кожен клас є модулем у тому сенсі, що Classклас є нащадком Moduleкласу. Однак:

class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module

module M; end
M.class # => Module

o = Object.new

o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)

2
Практично всі сучасні мови забезпечують певний ступінь самоаналізу, тому питання не дуже специфічне для Рубі.
Йоахім Зауер

Я розумію, я дав Рубі лише як приклад. Я не знаю, можливо, в деяких інших мовах із інтроспекцією є якісь "слабкі форми" ЛСП, але, якщо я правильно зрозумів принцип, він несумісний із самоаналізм.
Олексій

Я видалив "Рубін" із назви.
Олексій

2
Коротка відповідь - вони сумісні. Ось повідомлення в блозі, з яким я здебільшого згоден: Принцип заміщення
Ліскова

2
@ Алексі Властивості в цьому контексті є інваріантами об'єкта. Наприклад, незмінні об'єкти мають властивість, що їх значення не змінюються. Якщо ви подивитесь на хороші одиничні тести, вони повинні перевірити саме ці властивості.
K.Steff

Відповіді:


29

Ось власне принцип :

Нехай q(x)буде властивість, доказувальна щодо об'єктів xтипу T. Тоді q(y)слід доказувати для об'єктів yтипу, Sде Sє підтипом T.

І чудове резюме вікіпедії :

У комп'ютерній програмі зазначено, що якщо S є підтипом T, то об'єкти типу T можуть бути замінені об'єктами типу S (тобто об'єкти типу S можуть бути замінені об'єктами типу T) без зміни жодного з бажані властивості цієї програми (правильність, виконане завдання тощо).

І деякі відповідні цитати з газети:

Для цього потрібна більш сильна вимога, яка обмежує поведінку підтипів: властивості, які можна довести за допомогою специфікації припущеного типу об'єкта, повинні містити, навіть якщо об'єкт насправді є членом підтипу цього типу ...

Специфікація типу включає таку інформацію:
- ім'я типу;
- Опис простору значень типу;
- для кожного з методів типу:
--- його назва;
--- її підпис (включаючи сигнали виключень);
--- Його поведінка з точки зору передумов і пост-умов.

Отже, до питання:

Я правильно розумію, що Принцип заміщення Ліскова не можна спостерігати мовами, де об’єкти можуть перевіряти себе, як те, що зазвичай є у мовах, що набираються качками?

Немає.

A.classповертає клас.
B.classповертає клас.

Оскільки ви можете здійснювати той самий дзвінок на більш конкретний тип і отримувати сумісний результат, LSP утримується. Проблема полягає в тому, що за допомогою динамічних мов ви все одно можете телефонувати на результати, очікуючи їх наявності.

Але давайте розглянемо статичну, структурну (качкову) набрану мову. У цьому випадку A.classповертає тип із обмеженням, яким він має бути Aабо підтип A. Це дає статичну гарантію, що будь-який підтип Aповинен надавати метод T.class, результатом якого є тип, який задовольняє цьому обмеженню.

Це забезпечує більш чітке твердження про те, що LSP дотримується мов, які підтримують введення качок, і що будь-яке порушення LSP у чомусь на зразок Ruby відбувається більше через звичайне динамічне неправильне використання, ніж несумісність мовного дизайну.


1
"Оскільки ви можете зробити той самий дзвінок на більш конкретний тип і отримати сумісний результат, LSP утримується". LSP справедливий, якщо результати однакові, якщо я правильно його зрозумів. Можливо, може бути якась "тижнева" форма LSP щодо заданих обмежень, яка вимагає замість усіх властивостей задовольняти лише задані обмеження. У будь-якому випадку, я буду вдячний за будь-яку інформацію.
Олексій

@Alexey відредагований, щоб включити, що означає LSP. Якщо я можу використовувати B там, де очікую A, тоді LSP дотримується. Мені цікаво, як ви вважаєте, що .class Рубі, можливо, порушує це.
Теластин

3
@Alexey - Якщо ваша програма містить, fail unless x.foo == 42а підтип повертає 0, це те саме. Це не збій LSP, це нормальна робота вашої програми. Поліморфізм не є порушенням ЛСП.
Теластин

1
@Alexey - Звичайно. Припустимо, що це властивість. У такому випадку ваш код порушує LSP, оскільки він не дозволяє підтипам мати однакову семантичну поведінку. Але це не особливо особливе для динамічних мов або мов, які набираються на качках. Вони нічого не роблять у своїй мовній конструкції, що викликає порушення. Код, який ви написали, робить. Пам'ятайте, що LSP - це принцип проектування програми (звідси і слід у визначенні), а не математична властивість програм.
Теластин

6
@ Алекс: якщо ви пишете щось, що залежить від x.class == A, то саме ваш код порушує LSP , а не мову. Можна написати код, який порушує LSP майже в кожній мові програмування.
Андрес Ф.

7

У контексті LSP "властивість" - це те, що можна спостерігати на типі (або об'єкті). Зокрема, мова йде про "доказове майно".

Таке "властивість" могло б існувати foo()метод, який не має зворотної вартості (і відповідає договору, встановленому в його документації).

Переконайтеся, що ви не плутаєте цей термін із "властивістю", оскільки " classє власністю кожного об'єкта в Ruby". Така "властивість" може бути "властивістю LSP", але це автоматично не те саме!

Тепер відповідь на ваші запитання багато в чому залежить від того, наскільки суворо ви визначаєте "властивість". Якщо ви говорите "властивість класу A- це те .class, що поверне тип об'єкта", то Bнасправді це властивість має.

Якщо, проте, ви визначаєте «властивість» бути « .classповертається A», то очевидно , що Bце НЕ має цю властивість.

Однак друге визначення не є дуже корисним, оскільки ви, по суті, знайшли спосіб розкриття константи, який існує навколо.


Я можу придумати лише одне визначення "властивості" програми: для даного введення воно повертає задане значення, або, загальніше, при використанні в якості блоку в іншій програмі, інша програма для даного вводу поверне a задані значення. З цим визначенням я не бачу, що це означає, що " .classповерне тип об'єкта". Якщо це означає, що x.class == x.classце не є цікавою властивістю.
Олексій

1
@ Алекс: Я оновив своє запитання з уточненням того, що означає "властивість" у контексті LSP.
Йоахім Зауер

2
@ Алекс: заглядаючи в статтю, я не знаходжу конкретного визначення або "властивості". Це, мабуть, тому, що термін використовується в загальному значенні CS "щось, що можна спостерігати / доводити щодо якогось об'єкта". Це не має нічого спільного з іншим мірячим "полем об'єкта".
Йоахім Зауер

4
@ Алекс: Я не знаю, що більше можу вам сказати. Я використовую визначення "властивість - це якась якість або атрибута об'єкта". "колір" - це властивість фізичного, видимого об'єкта. "щільність" - властивість матеріалу. "мати вказаний метод" є властивістю класу / об'єкта.
Йоахім Зауер

4
@ Алекс: Я думаю, що ти кидаєш дитину разом з водою: Просто тому, що для деяких властивостей LSP не можна утримувати, це не означає, що воно марне або "не тримається жодною мовою". Але ця дискусія піде далеко сюди.
Йоахім Зауер

5

Як я розумію, немає нічого про самоаналіз, який би був несумісний з LSP. В основному, поки об'єкт підтримує ті ж методи, що й інші, вони повинні бути взаємозамінними. Тобто, якщо ваш код очікує Addressоб'єкт, то це не має значення , якщо це CustomerAddressабо WarehouseAddress, до тих пір, як забезпечити (наприклад) getStreetAddress(), getCityName(), getRegion()і getPostalCode(). Ви, звичайно, можете створити якийсь декоратор, який приймає об'єкт іншого типу та використовує інтроспекцію для надання необхідних методів (наприклад, DestinationAddressклас, який приймає Shipmentоб'єкт і представляє адресу судна у вигляді Address), але це не потрібно і, звичайно, не не перешкоджає застосуванню LSP.


2
@ Алекс: Об'єкти "однакові", якщо вони підтримують однакові методи. Це означає те саме ім’я, те саме число та тип аргументів, той самий тип повернення та однакові побічні ефекти (до тих пір, поки вони будуть видимими коду, що викликає). Методи можуть поводитись зовсім по-різному, але поки вони шанують контракт, це нормально.
TMN

1
@ Алекс: але чому я мав би такий контракт? Яке фактичне використання виконує цей договір? Якби я був такий контракт , я міг би просто замінити кожне входження в x.class.nameс 'A' , ефективно роблячи x.class.name непотрібним .
Йоахім Зауер

1
@ Алекс: знову ж таки: лише через те, що ви можете визначити договір, який не можна заповнити, розширивши інший клас, не порушує LSP. Це просто означає, що ви створили клас, що не розгортається. Якщо я визначу метод "повернення, якщо наданий блок коду закінчується в кінцевий час ", я також маю контракт, який неможливо виконати. Це не означає, що програмування марно.
Йоахім Зауер

2
@Alexey намагається визначити, чи x.class.name == 'A'є анти-зразком для набору тексту качок: зрештою, набирання качок походить від "Якщо вона стукає і ходить, як качка, це качка". Тож якщо він поводиться так, Aі шанує укладені контракти A, це Aприклад
К.Штефф

1
@Alexey Ви отримали чіткі визначення. Клас об'єкта не є частиною його поведінки чи контракту або тим, що ви хочете його назвати. Ви порівнюєте "властивість" з "об'єктним полем, таким як x.myField", яке, як було зазначено, НЕ те саме. У цьому контексті властивість більше нагадує математичну властивість, як тип інваріантів. Крім того, це анти-шаблон, щоб перевірити точний тип, якщо ви хочете набрати качку. Отже, яка у вас проблема з LSP і набиранням качок знову? ;)
Андрес Ф.

4

Переглянувши оригінальний документ Барбари Ліськов, я знайшов, як виконати визначення Вікіпедії, щоб ЛСП дійсно можна було задовольнити майже будь-якою мовою.

Перш за все, у визначенні важливе слово "доказовий" . Це не пояснено в статті Вікіпедії, а "обмеження" згадуються в інших місцях без посилання на нього.

Ось перша важлива цитата з статті:

Для цього потрібна більш сильна вимога, яка обмежує поведінку підтипів: властивості, які можна довести за допомогою специфікації припущеного типу об'єкта, повинні містити, навіть якщо об'єкт насправді є членом підтипу цього типу ...

І ось друге, що пояснює, що таке специфікація типу :

Специфікація типу включає таку інформацію:

  • Назва типу;
  • Опис простору значень типу;
  • Для кожного з методів типу:
    • Його назва;
    • Її підпис (включаючи сигнали про винятки);
    • Його поведінка з точки зору передумов і пост-умов.

Отже, LSP має сенс лише щодо заданих специфікацій типів , а для відповідної специфікації типу (наприклад, для порожньої) він може бути задоволений, мабуть, будь-якою мовою.

Я вважаю відповідь Теластина найближчим до того, що я шукав, оскільки "обмеження" були чітко зазначені.


Теластин, якби ти міг додати цитати до своєї відповіді, я вважаю за краще прийняти твої, ніж мої власні.
Олексій

2
розмітка не зовсім однакова, і я трохи змінив емпазію, але цитати були включені.
Теластин

Вибачте, Йоахім Зауер вже згадав про "доказові" властивості, які вам не сподобалися. Загалом, ви просто переробили наявні відповіді. Чесно кажучи, я не знаю, що ви шукаєте ...
Андрес Ф.

Ні, це не було пояснено "дозволено з чого?". Властивість x.class.name = 'A'є доказовою для всіх xкласів, Aякщо ви допускаєте занадто багато знань. Специфікація типу не була визначена, і його точне відношення до LSP також не було, хоча неофіційно були наведені деякі вказівки. Я вже знайшов те, що шукав у папері Ліськова, і відповів на моє запитання вище.
Олексій

Я думаю, що слова, які ти спонукав, є ключовими. Якщо документи із супертипом x, x.woozleякі дають будь-який із цих типів, дадуть вихід undefined, то жоден тип, для якого x.woozleне виходить, не undefinedбуде належним підтипом. Якщо супертип нічого не документує x.woozle, то факт, що використання x.woozleсупертипу буде мати результат, undefinedне означатиме нічого про те, що може бути зроблено для підтипу.
supercat

3

Цитуючи статтю Вікіпедії про LSP , "замінюваність - це принцип в об'єктно-орієнтованому програмуванні". Це принцип і частина дизайну вашої програми. Якщо ви пишете код, від якого залежить x.class == A, то ваш код порушує LSP. Зауважте, такий тип зламаного коду можливий також на Java, не потрібно вводити качку.

Ніщо в наборі качок по суті не порушує LSP. Тільки якщо ви неправильно використовуєте його, як у вашому прикладі.

Додаткова думка: все-таки чітко не перевіряється на предмет поразки класу об'єкта метою набору качки?


Андрес, ви можете дати своє визначення LSP, будь ласка?
Олексій

1
@Alexey Точне визначення LSP зазначено у Вікіпедії з точки зору підтипів. Неофіційне визначення є Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).. Точний клас об’єкта НЕ є однією з "бажаних властивостей програми"; в іншому випадку це буде суперечити не тільки друкуванню качки, а й загальному підтисканню, включаючи аромат Java.
Андрес Ф.

2
@ Алекс Також зауважте, що ваш приклад x.class == Aпорушує як LSP, так і набирання качок. Немає сенсу використовувати качки, якщо ви збираєтеся перевірити фактичні типи.
Андрес Ф.

Андрес, це визначення недостатньо точне, щоб я зрозумів. Яка програма, та чи інша програма? Що таке бажана властивість? Якщо клас знаходиться в бібліотеці, різні програми можуть вважати бажаними різні властивості. Не бачу, як рядок коду може порушувати LSP, тому що я думав, що LSP є властивістю пари класів у заданій мові програмування: або ( A, B) задовольняє LSP чи ні. Якщо LSP залежить від коду, який використовується в іншому місці, не пояснюється, який код дозволений. Сподіваюсь, що тут щось знайду: cse.ohio-state.edu/~neelam/courses/788/lwb.pdf
Олексій

2
@Alexey LSP відповідає (або ні) для конкретного дизайну. Це щось шукати в дизайні; це взагалі не властивість мови. Він не стає більш точним , ніж фактичне визначення: Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T. Очевидно, що x.classтут не одна з цікавих властивостей; інакше поліморфізм Яви теж не працював. У вашій "проблемі x.class" немає нічого властивого качки. Чи погоджуєтесь ви поки що?
Андрес Ф.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.