Недавно я унаследовал кодовую базу, в которой есть несколько крупных нарушителей Лискова. В важных классах. Это вызвало у меня огромное количество боли. Позвольте мне объяснить, почему.
У меня есть 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свойство. Однако, если хотя бы одна из этих ссылок неверна, все приложение может взорваться.
Учитывая, что кодовая база не имеет модульных тестов, это очень близко к тому, чтобы быть самым опасным сценарием, с которым может столкнуться разработчик. Если я не изменю нарушение, мне придется потратить огромное количество умственной энергии, отслеживая, над каким типом объекта оперируют в каждом методе, и если я исправлю нарушение, я могу в любой момент взорвать весь продукт.