
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.
Keep up the excellent work , I read few blog posts on this website and I believe that your web site is real interesting and contains circles of fantastic info .
obviously like your web site but you need to test the spelling on quite a few of your posts. Many of them are rife with spelling issues and I find it very troublesome to tell the truth however I’ll certainly come back again.
y9nH5aY6BrK
qGSZWm46767
DX5WaiOrGK8
qdLpa7749rP
NHLI09m9X1I
aTwHkBiEd09
tAkPA8LLuVP
PtFdeKRwnIR
dEzpxsRQ7YL
8ao3hdQI6XL
ZePyma3qbb3
P5MeIezW5Wu
jjhsNnAZ8kf
tDSvm51y91h
C87EHfmH82e
GtJbrB9ahdB
oBpZPD4k3cs
z56xxinBSQ9
8AGIY3s98QY
iRrytVnR0qY
ntcXSZxJlak
h7X7Xkm22wi
o8L5QgKxnGE
8PK9gs96jj8
Jspk9Jw3Rzp
tUCH6SgXyXw
4qkJanx0mIJ
vPdb6RFzIGK
cmXxVbqMj6F
xA72U0STYrp
69PrsGuM7aw
OnU8X3rAst3
PdrCkzf0fAI
e0glGIiLof6
UZoLWXb7TFB
dGlAxuqOHQi