Предположим, у меня есть следующий надуманный код:
abstract class Root
{
public abstract void PrintHierarchy();
}
class Level1 : Root
{
override public void PrintHierarchy()
{
Console.WriteLine("Level1 is a child of Root");
}
}
class Level2 : Level1
{
override public void PrintHierarchy()
{
Console.WriteLine("Level2 is a child of Level1");
base.PrintHierarchy();
}
}
Если я смотрю только на класс Level2
, я сразу вижу, что Level2.PrintHierarchy
следует принципу открытия / закрытия , потому что он что-то делает сам и вызывает базовый метод, который он переопределяет.
Однако, если я смотрю только на класс Level1
, он, кажется, нарушает OCP, потому что он не вызывает base.PrintHierarchy
- фактически, в C # компилятор запрещает это с ошибкой «Невозможно вызвать» абстрактный базовый член ".
Единственный способ заставить Level1
, по-видимому, следовать за OCP - это изменить Root.PrintHierarchy
на пустой виртуальный метод, но тогда я больше не могу полагаться на то, что компилятор заставит производные классы реализовывать PrintHierarchy
.
Реальная проблема, с которой я сталкиваюсь при ведении здесь кода, - это просмотр десятков override
методов, которые не вызывают base.Whatever()
. Если base.Whatever
является абстрактным, то хорошо, но если нет, то метод Whatever
может быть кандидатом на включение в интерфейс, а не конкретным переопределяемым методом - или класс или метод необходимо реорганизовать в какой-то другой способ, но в любом случае это явно указывает на плохой дизайн.
За исключением запоминания того, что Root.PrintHierarchy
является абстрактным или размещения комментария внутри Level1.PrintHierarchy
, есть ли у меня какие-либо другие варианты, чтобы быстро определить, нарушает ли класс, сформированный как Level1
, OCP?
В комментариях было много хороших обсуждений, а также несколько хороших ответов. У меня были проблемы с выяснением, что именно спросить здесь. Я думаю, что меня расстраивает то, что , как @Jon Hanna указывает , иногда виртуальный метод просто указывает «Вы должны реализовать меня», тогда как в других случаях это означает «вы должны расширить меня - если вы не сможете позвони в базовую версию, ты сломаешь мой дизайн! Но C # не предлагает какого-либо способа указать, кто из тех, кого вы имеете в виду, кроме того, что абстрактно или интерфейс явно является «обязательной реализацией» ситуации. (Если, конечно, в Code Contracts что-то не так, я думаю, что это немного выходит за рамки).
Но если бы язык имел , который имел обязательный для реализации и обязательный для расширения декоратор, он мог бы создать огромные проблемы для модульного тестирования, если бы его нельзя было отключить. Есть ли такие языки? Это скорее похоже на дизайн по контракту , поэтому я не удивлюсь, если бы это было, например, в Eiffel.
Конечный результат, вероятно, , как @ Jordão говорит , и он полностью контекстуален; но я собираюсь на некоторое время оставить обсуждение открытым, прежде чем приму какие-либо ответы.