Нещодавно я успадкував кодову базу, яка має в своєму розпорядженні кілька основних порушників Ліскова. У важливих класах. Це завдало мені величезної кількості болю. Дозвольте мені пояснити, чому.
Я маю 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властивість. Однак, помиляючись навіть в одному з цих посилань, вся програма може підірватись.
Зважаючи на те, що в кодовій базі немає одиничних тестів навколо, це досить близько до найбільш небезпечного сценарію, з яким може зіткнутися розробник. Якщо я не змінюю порушення, мені доведеться витратити величезні обсяги розумової енергії, відстежуючи, яким типом об'єкта керують у кожному методі, і якщо я виправляю порушення, я можу змусити весь продукт вибухнути в невідповідний час.