Нещодавно я успадкував кодову базу, яка має в своєму розпорядженні кілька основних порушників Ліскова. У важливих класах. Це завдало мені величезної кількості болю. Дозвольте мені пояснити, чому.
Я маю Class A
, що походить від Class B
. Class A
і Class B
поділитися купою властивостей, що Class A
переосмислює власну реалізацію. Налаштування або отримання Class A
ресурсу впливає на встановлення або отримання точно такої самої власності Class B
.
public Class A
{
public virtual string Name
{
get; set;
}
}
Class B : A
{
public override string Name
{
get
{
return TranslateName(base.Name);
}
set
{
base.Name = value;
FunctionWithSideEffects();
}
}
}
Якщо відкинути той факт, що це абсолютно жахливий спосіб зробити переклад у .NET, з цим кодом існує ряд інших проблем.
У цьому випадку Name
використовується в якості індексу і змінної регулювання потоку в ряді місць. Вищезазначені класи засмічені по всій кодовій базі як у сирому, так і у похідному вигляді. Порушення принципу заміщення Ліскова в цьому випадку означає, що мені потрібно знати контекст кожного виклику до кожної з функцій, які приймають базовий клас.
У коді використовуються як об'єкти, так Class A
і Class B
я не можу просто зробити Class A
абстрактним, щоб змусити людей використовувати Class B
.
Є кілька дуже корисних функцій утиліти, які працюють, Class A
та інші дуже корисні функції утиліти, які працюють на Class B
. В ідеалі я хотів би мати можливість використовувати будь-яку функцію утиліти, яка може працювати Class A
на Class B
. Багато функцій, які беруть на себе, Class B
можна було б легко Class A
виконати, якби не порушення LSP.
Найгірше в цьому те, що цей конкретний випадок насправді важко переробити, оскільки вся програма залежає від цих двох класів, працює на обох класах весь час і буде розбиватися на сто способів, якщо я зміню це (що я збираюся зробити все одно).
Що мені доведеться зробити, щоб це виправити, - це створити NameTranslated
властивість, яка буде Class B
версією Name
властивості, і дуже, дуже ретельно змінити кожне посилання на похідне Name
майно, щоб використовувати мою нову NameTranslated
властивість. Однак, помиляючись навіть в одному з цих посилань, вся програма може підірватись.
Зважаючи на те, що в кодовій базі немає одиничних тестів навколо, це досить близько до найбільш небезпечного сценарію, з яким може зіткнутися розробник. Якщо я не змінюю порушення, мені доведеться витратити величезні обсяги розумової енергії, відстежуючи, яким типом об'єкта керують у кожному методі, і якщо я виправляю порушення, я можу змусити весь продукт вибухнути в невідповідний час.