Принцип открытого-закрытого (OCP) гласит, что объект должен быть открыт для расширения, но закрыт для модификации. Я полагаю, что понимаю это и использую это вместе с SRP для создания классов, которые делают только одно. И я пытаюсь создать много небольших методов, которые позволяют извлечь все элементы управления поведением в методы, которые могут быть расширены или переопределены в некотором подклассе. Таким образом, я получаю классы, которые имеют много точек расширения, будь то через: внедрение и составление зависимостей, события, делегирование и т. Д.
Рассмотрим следующий простой расширяемый класс:
class PaycheckCalculator {
// ...
protected decimal GetOvertimeFactor() { return 2.0M; }
}
Теперь скажем, например, что OvertimeFactor
изменения до 1,5. Поскольку вышеупомянутый класс был разработан для расширения, я могу легко создать подкласс и вернуть другой OvertimeFactor
.
Но ... несмотря на то, что класс предназначен для расширения и соответствия OCP, я изменю отдельный метод, о котором идет речь, вместо того, чтобы создавать подклассы и переопределять этот метод, а затем перемонтировать мои объекты в мой контейнер IoC.
В результате я нарушил часть того, что пытается сделать OCP. Такое чувство, что я просто ленивый, потому что выше немного легче. Я неправильно понимаю OCP? Должен ли я действительно делать что-то другое? Вы по-разному используете преимущества OCP?
Обновление : основываясь на ответах, похоже, что этот надуманный пример плохой по ряду разных причин. Основная цель этого примера состояла в том, чтобы продемонстрировать, что класс был разработан для расширения путем предоставления методов, которые при переопределении будут изменять поведение открытых методов без необходимости изменения внутреннего или частного кода. Тем не менее, я определенно неправильно понял OCP.
источник
During the 1990s, the open/closed principle became popularly redefined to refer to the use of abstracted interfaces, where the implementations can be changed and multiple implementations could be created and polymorphically substituted for each other.
en.wikipedia.org/wiki/Open/closed_principleТаким образом, открытый закрытый принцип - это хитрость ... особенно, если вы пытаетесь применять его одновременно с YAGNI . Как мне придерживаться обоих одновременно? Примените правило трех . В первый раз, когда вы вносите изменения, вносите их напрямую. И во второй раз. В третий раз пришло время абстрагироваться от этого изменения.
Другой подход - «одурачить меня однажды ...», когда вам нужно внести изменения, примените OCP для защиты от этих изменений в будущем . Я бы почти зашел так далеко, чтобы предположить, что изменение ставки сверхурочных - это новая история. «Как администратор заработной платы, я хочу изменить сверхурочную работу, чтобы я мог соблюдать применимые законы о труде». Теперь у вас есть новый пользовательский интерфейс для изменения ставки сверхурочных, способ ее сохранения, и GetOvertimeFactor () просто запрашивает в своем хранилище, какова ставка сверхурочных.
источник
В приведенном вами примере коэффициент сверхурочных должен быть переменной или константой. * (Пример Java)
ИЛИ ЖЕ
Затем, когда вы расширяете класс, установите или переопределите коэффициент. «Магические числа» должны появляться только один раз. Это гораздо больше в стиле OCP и DRY (не повторяйте себя), потому что нет необходимости создавать целый новый класс для другого фактора, если используется первый метод, и нужно только изменить константу в одном идиоматическом место во втором.
Я бы использовал первое в тех случаях, когда было бы несколько типов калькуляторов, каждый из которых нуждался в различных постоянных значениях. Примером может служить шаблон цепочки ответственности, который обычно реализуется с использованием унаследованных типов. Объект, который может видеть только интерфейс (то есть
getOvertimeFactor()
), использует его для получения всей необходимой информации, в то время как подтипы беспокоятся о фактической информации, которую нужно предоставить.Второе полезно в тех случаях, когда константа вряд ли будет изменена, но используется в нескольких местах. Наличие одной константы для изменения (в маловероятном случае, когда это происходит) намного проще, чем ее установка повсеместно или получение из файла свойств.
Принцип Open-closed - это скорее призыв не изменять существующий объект, а предостережение, чтобы оставить интерфейс для них неизменным. Если вам нужно немного отличное поведение от класса или добавленная функциональность для конкретного случая, расширьте и переопределите. Но если требования к самому классу меняются (например, меняется коэффициент), вам нужно изменить класс. Нет смысла в огромной иерархии классов, большая часть которой никогда не используется.
источник
Я не вижу в вашем примере отличного представления об OCP. Я думаю, что на самом деле означает это правило:
Плохая реализация ниже. Каждый раз, когда вы добавляете игру, вам нужно будет изменить класс GamePlayer.
Класс GamePlayer никогда не нужно изменять
Теперь при условии, что моя GameFactory также поддерживает OCP, когда я хочу добавить другую игру, мне просто нужно создать новый класс, который наследуется от
Game
класса, и все должно работать.Слишком часто классы, подобные первому, создаются после нескольких лет «расширений» и просто никогда не подвергаются правильному рефакторингу по сравнению с исходной версией (или, что еще хуже, несколько классов остаются одним большим классом).
В качестве примера вы приводите OCP-иш. По моему мнению, правильный способ обработки изменений ставок сверхурочной работы будет в базе данных с сохранением исторических ставок, чтобы можно было повторно обрабатывать данные. Код все еще должен быть закрыт для модификации, потому что он всегда будет загружать соответствующее значение из поиска.
В качестве примера из реального мира я использовал вариант моего примера, и принцип Open-Closed действительно сияет. Функциональность действительно легко добавить, потому что мне просто нужно наследовать абстрактный базовый класс, и моя «фабрика» выбирает его автоматически, и «плееру» все равно, какую конкретную реализацию возвращает фабрика.
источник
В этом конкретном примере у вас есть то, что известно как «Волшебная ценность». По существу, жестко закодированное значение, которое может изменяться или не изменяться со временем. Я попытаюсь решить загадку, которую вы выражаете в общих чертах, но это пример того, что создание подкласса - это больше работы, чем изменение значения в классе.
Допустим, у нас есть
PaycheckCalculator
.OvertimeFactor
Бы более вероятно , будет ключом от информации о сотруднике. Почасовой служащий может получать сверхурочные бонусы, а наемный работник не получит ничего. Тем не менее, некоторые наемные работники получат прямое время из-за контракта, над которым они работали. Вы можете решить, что существуют определенные классы сценариев оплаты, и именно так вы бы построили свою логику.В базовом
PaycheckCalculator
классе вы делаете его абстрактным и указываете ожидаемые методы. Основные расчеты одинаковы, просто некоторые факторы рассчитываются по-разному. ВыHourlyPaycheckCalculator
бы затем реализоватьgetOvertimeFactor
метод и вернуть 1.5 или 2.0 в вашем случае может быть. ВашStraightTimePaycheckCalculator
бы реализоватьgetOvertimeFactor
вернуть 1,0. Наконец, третья реализация будетNoOvertimePaycheckCalculator
реализованаgetOvertimeFactor
для возврата 0.Ключ должен описывать только поведение в базовом классе, который предназначен для расширения. Детали частей общего алгоритма или конкретных значений будут заполнены подклассами. Тот факт, что вы добавили значение по умолчанию для
getOvertimeFactor
отведений, приводит к быстрому и простому «исправлению» одной строки вместо расширения класса, как вы предполагали. Это также подчеркивает тот факт, что есть усилия, связанные с расширением классов. Также приложены усилия для понимания иерархии классов в вашем приложении. Вы хотите спроектировать свои классы таким образом, чтобы свести к минимуму необходимость создания подклассов и обеспечить необходимую гибкость.Пища для размышления: когда наши классы инкапсулируют определенные факторы данных, как
OvertimeFactor
в вашем примере, вам может понадобиться способ извлечь эту информацию из какого-то другого источника. Например, файл свойств (поскольку он выглядит как Java) или база данных будут содержать значение, а вы будетеPaycheckCalculator
использовать объект доступа к данным для получения ваших значений. Это позволяет нужным людям изменять поведение системы без необходимости переписывать код.источник