Абстрактные методы и принцип Open-Closed - PullRequest
7 голосов
/ 07 октября 2010

Предположим, у меня есть следующий надуманный код:

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 говорит , и он полностью контекстуален; но я собираюсь на некоторое время оставить обсуждение открытым, прежде чем приму какие-либо ответы.

Ответы [ 3 ]

5 голосов
/ 07 октября 2010

Root определяет следующее: корневые объекты имеют метод PrintHierarchy.Он ничего не определяет больше о методе PrintHierarchy, чем этот.

Level1 имеет метод PrintHierarchy.Он не прекращает иметь метод PrintHierarchy, поэтому он никоим образом не нарушает принцип открытия / закрытия.

Теперь, более конкретно: переименуйте вашу «PrintHierarchy» в «Foo».Уровень 2 следует или нарушает принцип открытого / закрытого?

Ответ в том, что у нас нет подсказки, потому что мы не знаем, какова семантика "Foo".Поэтому мы не знаем, должен ли base.Foo вызываться после остальной части тела метода, перед остальной частью, в середине или вообще не.

Должен 1.ToString() return "System.ObjectSystem.ValueType1 "или" 1System.ValueTypeSystem.Object ", чтобы сохранить это притворство открытым / закрытым, или он должен назначить вызов base.ToString() неиспользуемой переменной, прежде чем возвращать" 1 "?

Очевидно, что ни один изэти.Он должен возвращать как можно более значимую строку.Его базовые типы возвращают как можно более значимую строку и расширяют ее, что не приносит пользы от обращения к ее базе.

Принцип открытия / закрытия означает, что когда я вызываю Foo (), я ожидаю, что произойдет некоторый Fooing, и я ожидаю некоторого соответствующего Fooing для Level1, когда я вызываю его на Level1, и некоторого соответствующего Fooing для Level2, когда я вызываю его на Level2.То, должен ли Level2 Fooing включать некоторый уровень Level1 Fooing, также зависит от того, что такое Fooing.

base - это инструмент, который помогает нам в расширении классов, а не требование.

3 голосов
/ 08 октября 2010

Вы не можете решить, следует ли система (или класс) следовать за OCP, просто рассматривая его статически, без контекстной информации.

Только если вы знаете каковы вероятные изменения дизайна, вы можете судить, следует ли оно OCP для этих конкретных изменений.

Нет языковой конструкции, которая могла бы помочь вам в этом.

Хорошей эвристикой было бы использование некоторой метрики, такой как нестабильность и абстрактность Роберта Мартина (pdf) , или метрик дляотсутствие единства, чтобы лучше подготовить ваши системы к изменению и иметь больше шансов следовать OCP и всем другим важным принципам OOD.

1 голос
/ 04 декабря 2011

OCP - это все о предварительных условиях и постусловиях: «когда вы можете только заменить его предварительное условие более слабым, а его постусловие более сильным».Я не думаю, что вызов base в переопределенных методах нарушает его.

...