В этом выпуске рассмотрим букву O, которая означает Open-Closed.
Предыдущая часть:
Данный принцип звучит почти эзотерически: любой функционал (класс, модуль) должен быть закрыт для изменений, но открыт для расширения.
Его можно понять неправильно, и тогда на выходе мы получим какой-то бред. Для этого достаточно истолковать всё буквально.
Например, у нас есть некий класс, пусть это будет опять User, а у него опять будет метод login(). Единожды написав метод login(), мы должны закрыть его от изменений. То есть всё, больше его трогать нельзя, крутитесь как хотите.
Например, хэш пароля вычислялся по одному криптоалгоритму, а теперь надо вычислять по другому. Что же нам делать? Надо изменить метод login(), но его менять нельзя :)
Но можно расширить класс User. Для этого сделаем класс User2, который наследуется от класса User, и у которого также будет метод login(), который перекрывает аналогичный метод родителя. И в этом методе мы можем написать новую реализацию логина.
Вот и получилось, что класс User закрыт для изменений, но открыт для расширений.
Но легко видеть, что такой путь заведёт в тупик. То, что мы сделали – всего лишь костыль, который не исправляет неправильный код, а делает обход вокруг него.
Таких костылей со временем может стать слишком много. Поэтому давайте разберёмся, о чём именно этот принцип.
Когда код менять нельзя?
Очевидно, что ваш код вы можете менять в любой момент и так, как захотите. Никто вам не указ. Проблема запрета изменений встаёт тогда, когда ваш код используется кем-то ещё. Тогда внесённые вами изменения, допустим, в публичную библиотеку могут привести к поломке тех приложений, которые её используют.
Аналогичным образом изменения могут повредить коллективной разработке, когда другие разработчики строят свой функционал на основе вашего.
Но и тут важно понять, что запрет изменений не означает приделывание костылей к плохо написанному коду. Если код изначально написан плохо, его НАДО исправить, без вариантов.
Принцип Open-Closed нужен не для затыкания дыр, а для начального проектирования. То есть думать о том, чтобы код был закрытым, нужно сразу.
Вернёмся к классу User. В данном случае нужно заранее (и это важно) понять, что криптоалгоритм возможно изменится.
Соответственно решить проблему можно несколькими способами. Первый это создать класс User и передать ему в конструктор тот криптоалгоритм, который будет использоваться.
user = new User('sha256');
Здесь мы его передаём просто как название, т.е. сам алгоритм должен быть как-то получен по названию. Чтобы не нарушать предыдущий принцип Single Responsibility, можно передать алгоритм как готовый класс:
user = new User(new Sha256());
Можно заметить, что мы применили паттерн "Композиция", который естественным образом получается в данной ситуации.
Другой вариант это передавать криптоалгоритм прямо в метод login:
user.login(new Sha256());
А это уже похоже на паттерн "Визитёр" :)
Так или иначе, мы добились желаемого: теперь мы можем иметь сколько угодно криптоалгоритмов и использовать их без изменения класса User.
Но есть нюанс
Как правило, такие знания приходят уже постфактум. В начале мы либо не можем предсказать, какие изменения понадобятся, либо планируем все возможные изменения, которые могут произойти. В первом случае мы получаем ограниченный код, который придётся переписывать, во втором – слишком избыточный код, который учитывает варианты, которые никогда не потребуются.
Исправить эти ситуации можно постоянной практикой, ну и да – переписывайте код, пока не получите правильный, принцип Open-Closed это вам не запрещает. Единственное исключение это когда код действительно используется где-то глубоко в продакшене, и кроме как вставлять костыли, поделать уже нечего.
Читайте дальше: