Я правильно розумію, що Принцип заміщення Ліскова не можна спостерігати мовами, де об’єкти можуть перевіряти себе, як те, що зазвичай є у мовах, що набираються на качках?
Наприклад, в 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)