Чи порушує державний зразок Принцип заміни Ліскова?


17

Це зображення взято із застосування дизайну та шаблонів, керованих доменом: із прикладами в C # та .NET

введіть тут опис зображення

Це діаграма класів для шаблону стану, в якій SalesOrderможе мати різні стани протягом свого життя. Дозволені лише певні переходи між різними станами.

Тепер OrderStateклас - це abstractклас, і всі його методи успадковуються підкласам. Якщо ми розглянемо підклас, Cancelledякий є кінцевим станом, який не дозволяє переходити до будь-яких інших станів, нам доведеться, щоб overrideусі його методи в цьому класі кидали винятки.

Тепер це не порушує принцип заміни Ліскова, оскільки підрозділ не повинен змінювати поведінку батьків? Чи змінює абстрактний клас на інтерфейс це?
Як це можна виправити?


Змінити OrderState, щоб бути конкретним класом, який за замовчуванням видаляє винятки "NotSupported" у всіх його методах?
Джеймс

Я не читав цю книгу, але мені здається дивним, що OrderState містить Cancel (), і тоді існує скасований стан, тобто інший підтип.
Ейфорія

@Euphoric чому це дивно? виклик cancel()змін стану на скасований.
Songo

Як? Як можна зателефонувати Скасувати на OrderState, коли екземпляр стану SalesOrder повинен змінитися. Я думаю, що вся модель неправильна. Я хотів би бачити повну реалізацію.
Ейфорія

Відповіді:


3

Ця конкретна реалізація, так. Якщо ви будете робити штати конкретними класами, а не абстрактними реалізаторами, то ви від цього підете.

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

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

Навпаки, я б запропонував зробити об'єкти більш розумними, як в об'єкті Model в шаблоні MVC, де модель вміє керувати собою, їй не потрібен зовнішній оркестратор, щоб керувати своїми внутрішніми роботами або причиною цього.

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

Але в цей момент це більше мистецтво, ніж інженерія, і, безумовно, суб'єктивний ваш підхід до деяких речей, що сказав, що принципи є хорошим керівництвом, і так, реалізація, яку ви перераховуєте, є порушенням LSP, але це не може бути виправлено. Просто будьте дуже обережні щодо SRP, коли використовуєте будь-який зразок такого характеру, і ви, ймовірно, будете в безпеці.


По суті, велика проблема полягає в тому, що орієнтація на об'єкт хороша для додавання нових класів, але ускладнює додавання нових методів. А у випадку з державою, як ви сказали, малоймовірно, що вам потрібно буде дуже часто поширювати код на нові штати.
hugomg

3
@ Jimmy Hoffa Вибачте, але що ви розумієте саме під тим, що "якщо ти зробиш штати конкретними класами, а не абстрактними реалізаторами, то ти підеш від цього". ?
Songo

@Jimmy: Я намагався реалізувати стан без зразка стану, маючи кожен компонент лише знати про свій стан. Це призвело до значних змін, які стали значно складнішими для впровадження та підтримки. З цього приводу я думаю, що використання державної машини (в ідеалі чужої бібліотеки, щоб змусити її трактувати як чорну скриньку), а не модель дизайну штату (таким чином, держави - це просто елементи всередині перерахунку, а не повноцінні класи, а переходи просто немічні краю) дає багато переваг державного зразка, вражаючи при цьому спроби розробників зловживати ним.
Брайан

8

підкарка не повинна змінювати поведінку батьків?

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

У Вікіпедії є досить довге пояснення , що дозволяє припустити, що порушить LSP:

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

  • Передумови не можна посилити в підтипі.
  • Послідовності не можуть бути ослаблені в підтипі.
  • Інваріанти супертипу повинні зберігатися в підтипі.
  • Обмеження історії ("правило історії"). Об'єкти вважаються модифікованими лише завдяки їх методам (інкапсуляція). Оскільки підтипи можуть вводити методи, відсутні в супертипі, то введення цих методів може дозволити зміни стану підтипу, які неприпустимі в супертипі. Обмеження історії забороняє це. Це був романний елемент, введений Ліськовим та Крилом. Порушення цього обмеження можна пояснити, визначивши MutablePoint як підтип ImmutablePoint. Це є порушенням обмеження історії, оскільки в історії Незмінюваної точки стан після створення завжди однаковий, тому він не може включати історію MutablePoint взагалі. Однак поля, додані до підтипу, можуть бути безпечно модифіковані, оскільки їх не можна спостерігати методами супертипу.

Особисто мені простіше просто запам'ятати це: Якщо я дивлюся на параметр у методі, який має тип А, чи не вдасться мені хтось, що передає підтип B, сюрпризи? Якби вони тоді, це є порушення LSP.

Чи кидання винятку є сюрпризом? Не зовсім. Це те, що може статися в будь-який час, я називаю метод "Ship" на OrderState або Granted або Shipped. Тому я маю це враховувати, і це насправді не є порушенням LSP.

Це сказав , я думаю, що є кращі способи вирішити цю ситуацію. Якби я писав це в C #, я б використовував інтерфейси і перевіряв реалізацію інтерфейсу, перш ніж викликати метод. Наприклад, якщо поточний OrderState не реалізує IShippable, не викликайте на ньому метод Ship.

Але тоді я також не використовував би модель держави для цієї конкретної ситуації. Форма стану набагато більше підходить до стану програми, ніж до стану об’єкта домену, як це.

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


Мені це здається, що ви плутаєте LSP з Принципом найменшого здивування / здивування, я вважаю, що LSP має більш чіткі межі, де це їх порушує, хоча ви застосовуєте більш суб'єктивний POLA, який базується на очікуванні, які є суб'єктивними ; тобто кожна людина очікує чогось іншого, ніж наступне. LSP базується на договірних та чітко визначених гарантіях, що надаються постачальником, а не на суб'єктивно оцінених сподіваннях, які вгадував споживач.
Джиммі Хоффа

@JimmyHoffa: Ти маєш рацію, і саме тому я прописав точне значення, перш ніж я заявив простіший спосіб запам'ятовування LSP. Однак якщо у мене є питання, що означає «сюрприз», тоді я повернусь і перевіряю точні правила LSP (чи дійсно ти можеш згадати їх за бажанням?). Винятки, що замінюють функціональність (або навпаки), є складним, оскільки він не порушує жодного конкретного пункту вище, що, мабуть, є підказкою, що з ним слід поводитися зовсім по-іншому.
pdr

1
Виняток - очікувана умова, визначена в контракті, на мій погляд, подумайте про NetworkSocket, він може мати очікуваний виняток при відправці, якщо він не відкритий, якщо ваша реалізація не кидає цей виняток для закритого сокета, ви порушуєте LSP , якщо в договорі зазначено протилежне, а ваш підтип викидає виняток, ви порушуєте LSP. Це більш-менш те, як я класифікую винятки в LSP. Що стосується запам'ятовування правил; Я можу помилитися з цього приводу і не соромтесь сказати мені це, але моє розуміння ЛСП проти ПОЛІ визначено в останньому реченні мого коментаря вище.
Джиммі Хоффа

1
@JimmyHoffa: Я ніколи не вважав POLA та LSP навіть віддаленими (я думаю про POLA в інтерфейсі), але тепер, коли ви це вказали, вони є такими. Я не впевнений, що вони конфліктують, або що один є більш суб'єктивним, ніж інший. Хіба LSP не є ранньою характеристикою POLA з точки зору інженерії програмного забезпечення? Так само, як я зриваюся, коли Ctrl-C не копіює (POLA), я також засмучуюся, коли ImmutablePoint є змінним, оскільки це насправді підклас MutablePoint (LSP).
пдр

1
Я б вважав CircleWithFixedCenterButMutableRadiusможливим порушенням LSP, якщо споживчий договір ImmutablePointвизначає, що якщо X.equals(Y), споживачі можуть вільно замінити X на Y, і навпаки, і, таким чином, повинні утриматися від використання класу таким чином, що така заміна спричинить проблеми. Тип може бути спроможним законно визначити equalsтаке, щоб усі екземпляри порівнювались як окремі, але така поведінка, ймовірно, обмежувала б його корисність.
supercat

2

(Це написано з точки зору C #, тому немає перевірених винятків.)

Згідно статті Вікіпедії про LSP , однією з умов LSP є:

Ніякі нові винятки не повинні бути викинуті методами підтипу, за винятком випадків, коли ці винятки - самі підтипи винятків, викинуті методами супертипу.

Як ви повинні це зрозуміти? Які саме "винятки, викинуті методами суперпертипу", коли методи супертипу абстрактні? Я думаю, що це винятки, які задокументовані як винятки, які можуть бути викинуті методами супертипу.

Це означає, що якщо OrderState.Ship()це задокументовано як щось на зразок "кидки, InvalidOperationExceptionякщо ця операція не підтримується поточним станом", то я думаю, що ця конструкція не порушує LSP. З іншого боку, якщо способи супертипу не задокументовані таким чином, LSP порушується.

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


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

Я думаю, що мовою, де винятки не перевіряються, кидання їх не є порушенням LSP. Це просто концепція самої мови, яку будь-коли і будь-який виняток можна кинути. Отже, будь-який код клієнта повинен бути готовий до цього.
Західло
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.