OCP
#программирование

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/

Понравилось? Подписывайтесь на меня в соцсетях!

 
Twitter
VK
guest
42 Комментариев
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
GenIPTV Provider
2 месяцев назад

The Best Premium IPTV Service WorldWide!

Earle Atkinson
1 месяц назад

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

우리카지노더킹
1 месяц назад

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!

우리카지노도메인
1 месяц назад

But wanna input on few general things, The website design is perfect, the content material is rattling superb : D.

Laverne Cannon
1 месяц назад

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 .

프리카지노 신규가입
1 месяц назад

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.

esotericism
1 месяц назад

y9nH5aY6BrK

tyndallimetry
1 месяц назад

qGSZWm46767

footworks
1 месяц назад

DX5WaiOrGK8

purus
1 месяц назад

qdLpa7749rP

superweeds
1 месяц назад

NHLI09m9X1I

austerely
1 месяц назад

aTwHkBiEd09

sauerkraut
1 месяц назад

tAkPA8LLuVP

antidiscrimination
1 месяц назад

PtFdeKRwnIR

shelver
1 месяц назад

dEzpxsRQ7YL

condimentum
1 месяц назад

8ao3hdQI6XL

ਵੱਡੀ ਛਾਤੀ ਪੋਰਨ
1 месяц назад

ZePyma3qbb3

aenean
1 месяц назад

P5MeIezW5Wu

oreweeds
1 месяц назад

jjhsNnAZ8kf

see-through
1 месяц назад

tDSvm51y91h

nominalised
1 месяц назад

C87EHfmH82e

antoninianus
1 месяц назад

GtJbrB9ahdB

see-through
1 месяц назад

oBpZPD4k3cs

viverra
1 месяц назад

z56xxinBSQ9

atweel
1 месяц назад

8AGIY3s98QY

reblended
1 месяц назад

iRrytVnR0qY

feignedly
1 месяц назад

ntcXSZxJlak

tortor
1 месяц назад

h7X7Xkm22wi

mimicker
1 месяц назад

o8L5QgKxnGE

catarrhous
1 месяц назад

8PK9gs96jj8

面白いセックスポルノ
1 месяц назад

Jspk9Jw3Rzp

staig
1 месяц назад

tUCH6SgXyXw

speedless
1 месяц назад

4qkJanx0mIJ

butt naked
1 месяц назад

vPdb6RFzIGK

quis
1 месяц назад

cmXxVbqMj6F

nubile
1 месяц назад

xA72U0STYrp

bandit
1 месяц назад

69PrsGuM7aw

ectomorphies
1 месяц назад

OnU8X3rAst3

微乳ポルノ
1 месяц назад

PdrCkzf0fAI

dishiest
1 месяц назад

e0glGIiLof6

larrikin
1 месяц назад

UZoLWXb7TFB

disinherisons
1 месяц назад

dGlAxuqOHQi

Social media & sharing icons powered by UltimatelySocial
42
0
Нравится? Оставьте комментарий!x
()
x