/ / Чи є обмеження LSP на посиленняпередумови, що суперечать припущенню, що необхідність зниження вказує на поганий дизайн - успадкування, дизайн класу, зниження, ієрархія класів, lsp

Чи є обмеження LSP на зміцнення передумов, що суперечать пропозиціям про те, що потреба у зниженні означає поганий дизайн - успадкування, клас-дизайн, downcasting, клас-ієрархія, lsp

Нещодавно я почав читати про принцип заміщення Ліскова (LSP), і я намагаюся повністю зрозумітинаслідки обмеження, що "передумови не можуть бути посилені в підтипі". Мені здається, що це обмеження суперечить принципу дизайну, який передбачає, що слід мінімізувати або повністю уникнути необхідності зниження базового рівня до похідного класу.

Тобто я починаю з Animal класу, і вивести тварин Dog, Bird, і Human. Обмеження LSP на передумови чітковідповідає природі, оскільки жоден собака, птах чи людина не повинні бути обмеженішими, ніж загальний клас тварин. Дотримуючись LSP, похідні класи потім додадуть особливі функції, такі як Bird.fly() або Human.makeTool() які не є загальними для Animal.

Для базового класу це здається трохи абсурдним Animal мати віртуальні методи для кожної можливої ​​ознаки кожного можливого підтипу тварини, але якщо це не так, то мені потрібно буде знизити Animal посилання на основний підтип доступуці унікальні особливості. Однак ця потреба впасти, як правило, вважається червоним прапором за поганий дизайн. Вікіпедія навіть заходить так далеко, що припускає, що це " через LSP таке зниження ставок вважається поганою практикою.

Так що ж мені не вистачає?

Бонусне запитання: Розглянемо ще раз ієрархію класів Animals описані вище. Очевидно, що це буде порушенням LSP, якщо Animal.setWeight(weight) потрібно лише невід’ємне число, але Human.setWeight(weight) посилив цю передумову і вимагав невід'ємного числа менше 1000. Але як щодо конструктора Human, що може виглядати так Human(weight, height, gender)? Чи було б порушенням LSP, якби конструктор наклав обмеження на вагу? Якщо так, то як цю ієрархію слід переробити, щоб дотримуватися чітких меж фізичних властивостей похідних тварин?

Відповіді:

1 для відповіді № 1

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

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

Дотримуючись LSP, похідні класи потім додадуть особливі функції, як от Bird.fly() або Human.makeTool() які не є загальними для Тварини.

Не зовсім. LSP припускає, що ви маєте справу лише з Animals. Наче це неможливо буде знизити. Отже, ваше Human, Bird та інші тварини можуть мати будь-які методи, конструктори чи що завгодно. Це взагалі не пов’язано з LSP. Вони просто повинні поводитися, як очікувалося, коли використовуються як Animalс

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

Два загальноприйняті підходи в основних мовах ООП:

  1. Зниження показників
  2. Шаблон відвідувачів

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


1 для відповіді № 2

Багато аспектів програмування передбачають компроміс іТверді принципи серед них. Якщо існують деякі види дій, які можна виконати однаковим чином майже для всіх похідних класу або реалізацій інтерфейсу, і насправді це не є основною метою інтерфейсу, але деякі конкретні похідні чи реалізації можуть мають кращий спосіб їх виконання, "Принцип розділення інтерфейсу" передбачає, що такі дії не включатимуться до загального інтерфейсу (*). У таких випадках це може бути корисно для коду, який отримує посилання на щось неспецифічне type, щоб перевірити, чи справжній об'єкт має певні "спеціальні" функції, і використовувати їх, якщо так. Наприклад, код, який отримує файл IEnumerable<Animal> і хоче знати, скільки предметів він містить, може перевірити, чи реалізовано це ICollection<Animal> або не-загальний ICollection [зауважте, що List<Cat> реалізує останнє, але не перше] і - якщо так - знижує і використовує Count метод]. У таких випадках немає нічого поганого в тому, щоб знизити, оскільки метод не вимагає, щоб передані екземпляри реалізовували ці інтерфейси - він просто працює краще, коли вони це роблять.

(*) ІМХО, IEnumerable повинен містити метод опису властивостей послідовності, наприклад, чи відомий підрахунок, чи буде він назавжди містити однакові елементи тощо, але це не означає.

Інше використання зниження ставок відбувається у випадках, колиодин матиме колекцію груп об'єктів і знатиме, що конкретні екземпляри об'єктів у кожній групі "сумісні" між собою, навіть якщо об'єкти однієї групи можуть бути несумісні з об'єктами іншої. Наприклад, MaleCat.MateWith() метод може приймати лише екземпляр FemaleCat, і FemaleKangaroo.MateWith() з може приймати лише екземпляр MaleKangaroo(), але найбільш практичним способом для Ноя отримати колекцію спарованих пар тварин було б, щоб кожен тип тварин мав MateWith() метод, який приймає Animal і зниження до відповідного типу (і, можливо, також мають файл CanMateWith() майно). Якщо MatingPair побудований, що містить a FemaleHamster і MaleWolf, спроба викликати файл Breed() метод для цієї пари зазнав би невдачі під час виконання, але якщокод дозволяє уникнути побудови несумісних парних пар, такі відмови ніколи не повинні відбуватися. Зауважте, що дженерики можуть суттєво зменшити потребу у такому зниженні, але не повністю усунути її.

При визначенні того, чи зниження ставки порушує LSP, питання в 50 000 доларів полягає в тому, чи підтримує метод контракт на все, що може бути передано. Якщо MateWith() Контракт методу визначає, що він гарантує корисну поведінку лише у певних випадках Animal для котрого CanMateWith() повернув істину, той факт, що він не вдасться отримати деякі підтипи Animal не буде порушенням LSP.Загалом корисно мати методи, які відкидають під час компіляції об'єкти, тип яких не гарантовано придатними для використання, але в деяких випадках код може мати знання про взаємозв'язки між типами певних екземплярів об'єктів, які не можуть бути виражені синтаксично [наприклад той факт, що а MatingPair проведе два Animal екземпляри, які можна успішно розводити]. Незважаючи на те, що даундайтинг часто є запахом коду, у ньому немає нічого поганого, коли він використовується способами, що відповідають контракту об'єкта.