Принцип Open/Closed - хорошая идея?

Этот вопрос не о том, что такое OCP. И я также не ищу упрощенных ответов.

Итак, вот почему я спрашиваю об этом. OCP была впервые описана в конце 80-х годов. Он отражает мышление и контекст того времени. Выяснилось, что изменение исходного кода для добавления или изменения функциональности после того, как код уже был протестирован и запущен в производство, окажется слишком рискованным и дорогостоящим. Поэтому идея заключалась в том, чтобы как можно больше не изменять существующие исходные файлы и добавлять только к базе кода в виде подклассов (расширений).

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

Идея рефакторинга гораздо более поздняя. В то время изощренные IDE, которые позволяли автоматизировать операции рефакторинга, были явно несущественны. Даже сегодня многие разработчики не используют лучшие инструменты рефакторинга. Дело в том, что такие современные инструменты позволяют разработчику сменить буквально тысячи строк кода безопасно за несколько секунд.

Наконец, сегодня идея автоматизированного тестирования разработчиков (тесты на единицу/интеграцию) широко распространена. Существует множество бесплатных и сложных инструментов, которые его поддерживают. Но что хорошего создает и поддерживает большой автоматизированный набор тестов, если мы никогда/редко меняем существующий код? Новый код, как требует OCP, потребует только новых тестов.

Итак, действительно ли OCP имеет смысл сегодня? Я так не думаю. Вместо этого я бы предпочел изменить существующий код при добавлении новых функций, если новая функциональность не требует новых классов. Это позволит сохранить кодовую базу проще, меньше и намного легче читать и понимать. Риск нарушения предыдущих функциональных возможностей будет осуществляться с помощью инструментов VCS, рефакторинга и автоматизированных наборов тестов.

Ответ 1

OCP имеет большой смысл, когда вы не являетесь потребителем вашего кода. Если я пишу класс, и я или моя команда пишу все классы, которые его потребляют, я согласен. Рефакторинг по мере того, как все меняется, не является огромной сделкой.

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

Кроме того, если вы просто реорганизуете свой класс для удовлетворения всех потребностей, в результате вы получите раздутый класс. Если вы разработали класс, чтобы позволить потребителям расширять ваш класс, а не изменять его, у вас действительно не было бы этой проблемы.

Ответ 2

Я никогда не слышал об этом. Возможно, вы ссылаетесь на что-то еще, но OCP, который я знаю, говорит: "Модуль/класс должен быть открыт для расширения, но закрыт для модификации, то есть вы не должны изменять исходный код модуля для его улучшения, но модуль или объект должен быть легко расширяться.

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

Итак, да, принцип Open/Closed действительно очень эффективный и неплохая идея.

UPDATE:

Я вижу, что главный конфликт здесь - между кодом, который все еще находится в разработке, и кодом, который уже отправлен и используется кем-то. Поэтому я пошел и проверил Бертран Майер, автор этого принципа. Он говорит:

Модуль называется замкнутым, если он доступен для использования другими модулями. Это предполагает что модуль получил четкое, стабильное описание (его интерфейс в ощущение скрытности информации). На уровне реализации закрытие для модуля также подразумевает, что вы можете скомпилировать его, возможно, сохранить его в библиотеке и сделать его доступным для других (своих клиентов) использовать.

Итак, принцип Open/Closed относится только к стабильным, готовым для компиляции и использования объектов.

Ответ 3

Хорошо, так вот мой ответ.

Я не могу засвидетельствовать историческое происхождение принципа, но он по-прежнему часто используется в наше время. Я не думаю, что это опасно для изменения действующего кода (хотя это, конечно, есть), о том, чтобы позволить вам выделять идеи.

Предположим, что у нас есть компонента

public class KnownFriendsFilter{
  IList<People> _friends;
  public KnownFriendsFilter(IList<People> friends) {
    _friends = friends;
  }
  public IList<Person> GetFriends(IList<Person> people) {
    return people.Where(p=>_friends.Contains(p)).ToList();
  }
}

Теперь скажите алгоритм, в котором этот конкретный компонент нуждается в небольшой модификации - например, вы хотите удостовериться, что исходный список, переданный в, содержит отдельных людей. Это то, что было бы проблемой для KnownFriendsFilter, поэтому обязательно измените класс.

Однако существует разница между этим классом и поддерживаемой функцией.

  • Этот класс действительно предназначен для фильтрации множества людей для известных друзей
  • Функция, которую он поддерживает, - это найти всех друзей из массива людей.

Разница в том, что функция связана с функцией, в то время как класс связан с реализацией. Большинство запросов, которые мы получаем, чтобы изменить эту функцию, выходят за рамки конкретной ответственности класса.

Например, скажем, мы хотим добавить черный список любых имен, которые начинаются с буквы "X" (потому что эти люди, очевидно, космические, а не наши друзья). Что-то, что поддерживает эту функцию, но на самом деле не является частью того, что представляет собой этот класс, придерживаться его в классе было бы неудобно. Как насчет того, когда приходит следующий запрос, теперь приложение используется исключительно женоненавистниками, и любые женские имена также должны быть исключены? Теперь вам нужно добавить логику, чтобы решить, является ли имя мужчиной или женщиной в классе - или, по крайней мере, знать о каком-то другом классе, который знает, как это сделать - класс растет в обязанностях и становится очень раздутым! А как насчет сквозных проблем? Теперь мы хотим регистрироваться всякий раз, когда мы фильтруем массив людей, тоже ли это происходит?

Было бы лучше разделить интерфейс IFriendsFilter и обернуть этот класс в декораторе или переустановить его как цепочку ответственности на IList. Таким образом, вы можете разместить каждую из этих обязанностей в своем классе, который поддерживает именно эту проблему. Если вы добавляете зависимости, то любой код, который использует этот класс (и его централизованно используется в нашем приложении), не должен вообще меняться!

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

Ответ 4

Итак, действительно ли OCP имеет смысл сегодня? Я так не думаю.

Иногда это происходит:

  • Когда вы выпустили базовый класс для клиентов и не можете легко его модифицировать на всех компьютерах ваших клиентов (см., например, "DLL Hell" )

  • Когда вы являетесь клиентом, который сам не написал базовый класс и не поддерживает его

  • В более общем плане, любая ситуация, когда базовый класс используется более чем одной командой и/или более чем для проекта

См. также Закон Конвей.

Ответ 5

Интересный вопрос, основанный на строгом определении открытого закрытого принципа, я вижу, откуда вы.

Я пришел к тому, чтобы определить принцип открытого закрытия несколько иначе, и этот принцип, который, как я думаю, должен применяться, и это применять его гораздо шире.

Мне нравится говорить, что все мои классы (в целом), участвующие в приложении, должны быть закрыты для изменения и открыты для расширения. Таким образом, принцип заключается в том, что если мне нужно изменить поведение и/или работу приложения, я фактически не модифицирую класс, а добавляю новый, а затем изменяю отношения, чтобы указать на это новое (в зависимости от размера изменения). Если я выполняю единственную ответственность и использую инверсию управления, это должно произойти. Что тогда происходит, так это то, что все изменения становятся расширениями. Теперь они могут действовать как по-новому, так и по-новому и меняться между ними = изменение отношений.

Ответ 6

Дело в том, что такие современные инструменты позволяют разработчику сменить буквально тысячи строк кода, безопасно, через несколько секунд.

Это нормально, если у вас есть "разработчик". Если вы работаете в командах, безусловно, с контролем версий, вероятно, с ветвлением и слиянием, то способность гарантировать, что изменения от разных людей, как правило, концентрируются в разных файлах, очень важна для того, чтобы контролировать, что происходит.

Вы могли бы представить специфические для языка инструменты слияния/разветвления, которые могли бы выполнять три рефакторинга, выполняемые параллельно, и объединить их так же легко, как изменения отдельных файлов. Но таких инструментов не существует, и если бы они это сделали, я не хотел бы полагаться на них.