
SOLID. Что значит O
Это второй пост из цикла статей про SOLID. Сегодня мы разберем, что означает буква O в этой аббревиатуре. Как обычно, мы рассмотрим принцип на примере и поговорим о том, как он нам помогает, что было бы без него и зачем он вообще нужен.
O значит OCP
Что, в свою очередь, расшифровывается как Open-Closed Principle, он же принцип открытости/закрытости. Чисто теоретическое описание доступно на википедии, а мы сразу переходим к примеру.
OCP на практике
Представьте, что вы возвращаетесь в московский аэропорт «Домодедово» из своего отпуска по Европе. Вы выходите из самолета, проходите пару длинных коридоров и оказываетесь у стоек паспортного контроля.
Перед вами две стойки – для граждан Российской Федерации и для граждан других государств. В аэропорт одновременно приходит множество рейсов, и вполне логично для своих граждан выделить отдельную стойку, чтобы они получили приоритет и максимально быстро вернулись домой. Вы пользуетесь этой возможностью.
Это – правильная и грамотная реализация OCP. Давайте теперь посмотрим, что произошло бы, если бы проектировщики аэропорта нарушили этот принцип.
Игнорирование OCP
Давайте на минуту представим, что аэропорт по-прежнему хочет предоставить своим гражданам преимущество, но при этом не следует принципу открытости/закрытости.
Итак, перед стойкой паспортного контроля стоит очередь из граждан разных государств, в том числе и РФ. Вдруг выходит сотрудник аэропорта и объявляет: «Внимание! Ближайшие два часа обслуживаются только граждане Российской Федерации!».
Граждане РФ ликуют. Все остальные в аэропорту:
Что произошло?
А произошло вот что: аэропорт решил добавить новую фичу (предоставление приоритета своим гражданам) и при этом изменил реализацию паспортного контроля таким образом, что это оказалось неожиданностью для всех.
Как нужно было сделать? Оставить стойку в покое и никак не менять порядок паспортного контроля на ней. Вместо этого добавить еще одну стойку для граждан РФ. Собственно, как это и реализовано во всех аэропортах.
Пример кода
Давайте теперь посмотрим, как бы это выглядело в коде. Представим, что вы – разработчик класса PassportControl
.
Вы отвечаете за паспортный контроль прилетевших граждан. Вы проводите идентификацию пассажира и разного рода проверки. Например, граждане с просроченными паспортами и визами не должны пройти паспортный контроль. Если же все хорошо, вы ставите печать о въезде в страну и пропускаете гражданина.
public class PassportControl {
public void process(List<Passenger> passengers) {
passengers.forEach(passenger -> pass(passenger));
}
private void pass(Passenger passenger) {
if (validate(passenger)) {
// поставить печать и пропустить
} else {
// вызвать сотрудников для дальнейших действий
}
}
private boolean validate(Passenger passenger) {
Passport passport = passenger.getPassport();
boolean passportValid = checkPassportIsValid(passport);
boolean visaValid = checkVisas(passport.getVisas());
return passportValid && visaValid;
}
private boolean checkPassportValid(Passport passport) {
// return false если паспорт просрочен или невалиден
return true;
}
private boolean checkVisas(List<Visa> visas) {
// return false, если среди активных виз нет визы РФ
return true;
}
}
Аэропорт пользуется вашим классом в одном из своих сервисов:
public class ArrivalService {
private PassportControl passportControl;
public ArrivalService(PassportControl passportControl) {
this.passportControl = passportControl;
}
public void process(List<Passenger> passengers) {
passportControl.process(passengers);
}
}
И вроде бы все хорошо
Правда, лишь до тех пор, пока вы не решаете поменять реализацию своего метода pass
. Вы хотите, чтобы с 18:00 до 20:00 обслуживались только граждане РФ, и обновляете метод соответствующим образом:
private void pass(Passenger passenger) {
if (getCurrentHour() >= 18 && getCurrentHour() < 20) {
if (passenger.getCountryCode() != CountryCode.RU) {
return; // выходим из метода и не обслуживаем пассажира
}
}
if (validate(passenger)) {
// поставить печать и пропустить
} else {
// вызвать сотрудников для дальнейших действий
}
}
Аэропорт, в свою очередь, обновляется до последней версии вашего класса и продолжает им как ни в чем не бывало пользоваться. Правда, ровно до 18 часов, когда у стойки вдруг образуется толпа недовольных иностранцев. Как результат, аэропорту придется разбираться в причинах недовольства, проводить анализ логов и выполнять дебаг.
И все это при условии, что ни строчки кода в самом сервисе аэропорта не поменялось! Поменялась лишь реализация сторонней функции, которая аэропортом используется.
Проигнорировав OCP, вы породили ошибки в логике зависимого от вашего кода приложения.
Как надо было сделать
Нужно было оставить функцию pass
в исходном состоянии, вместо этого добавить новую функцию processPriority
с дополнительной функциональностью:
public class PassportControl {
public void process(List<Passenger> passengers) {
passengers.forEach(passenger -> pass(passenger));
}
public void processPriority(List<Passenger> passengers, CountryCode priority) {
passengers
.stream()
.filter(passenger -> priority.equals(passenger.getCountryCode()))
.forEach(passenger -> pass(passenger));
}
private void pass(Passenger passenger) {
if (validate(passenger)) {
// поставить печать и пропустить
} else {
// вызвать сотрудников для дальнейших действий
}
}
private boolean validate(Passenger passenger) {
Passport passport = passenger.getPassport();
boolean passportValid = checkPassportValid(passport);
boolean visaValid = checkVisa(passport.getVisas());
return passportValid && visaValid;
}
private boolean checkPassportValid(Date until) {
// return false если паспорт просрочен или невалиден
return true;
}
private boolean checkVisa(List<Visa> visas) {
// return false, если среди активных виз нет визы РФ
return true;
}
}
Второй метод, processPriority(passenger, countryCode)
, фильтрует граждан по заданному коду страны и проверяет только тех пассажиров, которые удовлетворяю условию.
Так как вы не меняете реализацию текущего метода, а лишь добавляете новый, работа аэропорта никак не меняется, а все пассажиры остаются довольны.
Когда вы рассказываете о своей новой функции своим клиентам (аэропорту), они вполне могут захотеть обновить свой код, чтобы начать эту функцию использовать:
public class ArrivalService {
private PassportControl passportControl;
public ArrivalService(PassportControl passportControl) {
this.passportControl = passportControl;
}
public void process(List<Passenger> passengers) {
passportControl.process(passengers);
}
public void processRussianCitizens(List<Passenger> passengers) {
passportControl.process(passengers, CountryCode.RU);
}
}
Вывод
OCP помогает расширять функциональность вашего кода таким образом, чтобы это не отразилось на работе завязанных на вас сервисов.
В примере выше в коде аэропорта не поменялась ни одной строчки, но работоспособность была нарушена. И все это из-за того, что всего лишь одна приватная функция библиотеки, от которой зависит аэропорт, изменила реализацию.
Если бы мы следовали принципу открытости/закрытости изначально и расширяли бы функциональность грамотно, то никаких проблем не возникло бы.
Заключение
Не нарушай работу зависимых от тебя частей системы. Следуй принципу OCP.
Предыдущие статьи из цикла SOLID:
S: https://baddev.ru/solid-srp/
Понравилось? Подписывайтесь на меня в соцсетях!


The Best Premium IPTV Service WorldWide!
Definitely believe that which you said. Your favorite justification appeared to be on the net the easiest thing to be aware of. I say to you, I certainly get irked while people consider worries that they plainly do not know about. You managed to hit the nail upon the top as well as defined out the whole thing without having side-effects , people can take a signal. Will probably be back to get more. Thanks
My brother suggested I might like this website. He was entirely right. This post truly made my day. You cann’t imagine simply how much time I had spent for this information! Thanks!
But wanna input on few general things, The website design is perfect, the content material is rattling superb : D.