Артыкулы

Адкрыты / Закрыты, паводле прынцыпу ТВАРДЫ

Суб'екты праграмнага забеспячэння (класы, модулі, функцыі і г.д.) павінны быць адкрыты для пашырэння, але закрыты для рэдагавання.

Распрацоўка праграмнага забеспячэння: модуляў, класаў і функцый такім чынам, каб пры неабходнасці новых функцый мы не павінны мадыфікаваць існуючы код, а пісаць новы код, які будзе выкарыстоўвацца існуючым кодам. Гэта можа здацца дзіўным, асабліва з такімі мовамі, як Java, C, C ++ або C #, дзе гэта адносіцца не толькі да самага зыходнага кода, але і да двайковага файла. Мы хочам стварыць новыя функцыі спосабамі, якія не патрабуюць пераразмеркавання існуючых бінарных файлаў, выкананых файлаў альбо DLL-файлаў.
OCP у цвёрдым кантэксце

 

SRP і OCP дапаўняюць адзін аднаго

Мы ўжо бачылі прынцып SRP адзінай адказнасці, які сцвярджае, што модуль павінен мець толькі адну прычыну для змены. Прынцыпы OCP і SRP дапаўняюць адзін аднаго. Код, распрацаваны ў адпаведнасці з прынцыпам SRP, таксама будзе паважаць прынцыпы OCP. Калі ў нас ёсць код, у якога ёсць толькі адна прычына для змены, увядзенне новай функцыі створыць другасную прычыну гэтага змены. Такім чынам, SRP і OCP будуць парушаныя. Сапраўды гэтак жа, калі ў нас ёсць код, які павінен змяняцца толькі пры змене яго асноўнай функцыі і павінен заставацца нязменным пры даданні новай функцыянальнасці, паважаючы, такім чынам, OCP, ён у асноўным будзе паважаць і SRP.
Гэта не азначае, што SRP заўсёды вядзе да OCP альбо наадварот, але ў большасці выпадкаў, калі прытрымліваецца аднаго з іх, дамагчыся другога даволі проста.

 

Прыклад парушэння прынцыпу OCP

З чыста тэхнічнага пункту гледжання адкрыты / закрыты прынцып вельмі просты. Простая сувязь паміж двума класамі, як прыведзеная ніжэй, парушае прынцып OCP.

Клас User выкарыстоўвае непасрэдна клас Logic. Калі нам трэба рэалізаваць другі клас Logic такім чынам, каб мы маглі выкарыстоўваць як бягучы, так і новы, існуючы клас Logic трэба будзе змяніць. Карыстальнік непасрэдна прывязаны да рэалізацыі логікі, мы не можам даць новую логіку, не закранаючы бягучую. І калі мы гаворым пра статычна набраныя мовы, клас User, верагодна, таксама патрабуе мадыфікацый. Калі мы гаворым пра скампіляваныя мовы, безумоўна, і выканальны файл карыстальніка, і выканальны файл Logic, альбо дынамічная бібліятэка патрабуюць перакампіляцыі і дастаўкі, пераважна пазбягаць, калі гэта магчыма.

Спасылаючыся на папярэднюю схему, мы можам зрабіць выснову, што любы клас, які непасрэдна выкарыстоўвае іншы клас, можа прывесці да парушэння прынцыпу Open / Closed. 
Дапусцім, мы хочам напісаць клас, здольны забяспечыць прагрэс "у працэнтах" загружанага файла праз наша дадатак. У нас будзе два асноўныя класы, "Прагрэс" і "Файл", і я мяркую, што мы хацелі б выкарыстоўваць іх наступным чынам:

 

функцыя testItCanGetTheProgressOfAFileAsAPercent () {
     $ файл = новы файл ();
     $ файл-> даўжыня = 200;
     $ файл-> адпраўлены = 100;
     $ progress = новы прагрэс ($ файл);
     $ this-> assertEquals (50, $ progress-> getAsPercent ());
}

У гэтым кодзе мы - карыстальнікі Progress. Мы хочам атрымаць значэнне ў працэнтах, незалежна ад фактычнага памеру файла. Мы выкарыстоўваем файл у якасці крыніцы інфармацыі. Файл мае даўжыню ў байтах і поле, якое называецца адпраўленым, якое ўяўляе аб'ём дадзеных, адпраўленых загрузніку. Нам усё роўна, як гэтыя значэнні абнаўляюцца ў дадатку. Мы можам выказаць здагадку, што для нас гэта робіцца нейкая магічная логіка, таму ў тэсце мы можам іх дакладна ўсталяваць.

 

клас File {
     дзяржаўная даўжыня $;
     грамадскі $ адпраўлены;
}

 

Клас File - гэта проста просты аб'ект дадзеных, які змяшчае два поля. Зразумела, ён таксама павінен утрымліваць іншую інфармацыю і паводзіны, напрыклад, імя файла, шлях, адносны шлях, бягучы каталог, тып, дазволы і г.д.

 

прагрэс класа {

     прыватны $ файл;

     функцыя __construct (Файл $ файл) {
          $ this-> файл = $ файл;
     }

     функцыя getAsPercent () {
          вярнуць $ this-> файл-> адпраўлены * 100 / $ this-> файл-> даўжыня;
     }

}

Прагрэс - гэта проста клас, які прымае файл у сваім канструктары. Для нагляднасці мы ўказалі тып зменнай у параметрах канструктара. У Progress існуе адзіны карысны метад, getAsPercent (), які прымае адпраўленыя значэнні і даўжыню з файла і ператварае іх у працэнты. Проста, і гэта працуе.

Здаецца, гэты код правільны, аднак ён парушае прынцып Адкрыта / Закрыта.

Але чаму?

І як?

 

Паспрабуем змяніць патрабаванні

Кожнаму дадатку, каб развівацца з цягам часу, спатрэбяцца новыя функцыі. Новай функцыяй нашага прыкладання можа стаць дазвол струменевай перадачы музыкі, а не проста загрузка файлаў. Даўжыня файла прадстаўлена ў байтах, працягласць музыкі ў секундах. Мы хочам прапанаваць слухачам прагрэс, але ці можам мы паўторна выкарыстаць напісаны вышэй клас?

Не, мы не можам. Наш прагрэс звязаны з File. Ён можа кіраваць толькі інфармацыяй пра файлы, хоць ён таксама можа прымяняцца да музычнага кантэнту. Але для гэтага нам трэба яе змяніць, мы павінны зрабіць так, каб Progress ведаў музыку і файлы. Калі б наш дызайн адпавядаў OCP, нам не трэба было б дакранацца да File або Progress. Мы маглі б проста выкарыстаць існуючы прагрэс і прымяніць яго да музыкі.

 

Інавацыйны бюлетэнь
Не прапусціце самыя важныя навіны пра інавацыі. Падпішыцеся, каб атрымліваць іх па электроннай пошце.

Магчымае рашэнне

Дынамічна набіраныя мовы маюць перавагу ў кіраванні тыпамі аб'ектаў падчас выканання. Гэта дазваляе нам выдаліць падказку з канструктара Progress, і код будзе працягваць працаваць.

прагрэс класа {

     прыватны $ файл;

     функцыя __construct ($ файл) {
         $ this-> файл = $ файл;
     }

    функцыя getAsPercent () {
         вярнуць $ this-> файл-> адпраўлены * 100 / $ this-> файл-> даўжыня;
     }

}

Цяпер мы можам запусціць што заўгодна ў Progress. І пад любым словам я маю на ўвазе літаральна ўсё:

клас Музыка {

дзяржаўная даўжыня $;
грамадскі $ адпраўлены;

публічны $ мастак;
публічны $ альбом;
грамадскі $ releaseDate;

функцыя getAlbumCoverFile () {
return 'Выявы / Вокладкі /'. $ this-> мастак. '/'. $ this-> альбом. '.png';
}
}

І такі клас музыкі будзе працаваць выдатна. Мы можам лёгка праверыць яго тэстам, вельмі падобным на File.
функцыя testItCanGetTheProgressOfAMusicStreamAsAPercent () {
$ music = новая музыка ();
$ музыка-> даўжыня = 200;
$ музыка-> адпраўлена = 100;

$ progress = новы прагрэс ($ музыка);

$ this-> assertEquals (50, $ progress-> getAsPercent ());
}

Такім чынам, у асноўным любы вымяраемы змест можна выкарыстоўваць з класам Progress. Магчыма, нам варта выказаць гэта ў кодзе, таксама змяніўшы імя зменнай:

прагрэс класа {

прыватны $ вымяральны кантэнт;

функцыя __construct ($ mjerljiviContent) {
$ this-> измеримыйКонтент = $ измеримыйКонтент;
}

функцыя getAsPercent () {
вярнуць $ this-> измеримыйКонтент-> адпраўлены * 100 / $ гэты-> вымяральныКонтэнт-> даўжыня;
}

}

Калі мы ўказалі File у якасці падказкі, мы з аптымізмам глядзелі на тое, што наш клас можа апрацаваць. Гэта было відавочна, і калі б што-небудзь яшчэ прыйшло, нам паведамілі б вялікую памылку.

Uклас, які перавызначае метад базавага класа, так што кантракт базавага класа не выконваецца вытворным класам. 

Мы не хочам у канчатковым выніку спрабаваць выклікаць метады альбо адкрыць палі для аб'ектаў, якія не адпавядаюць нашаму кантракту. Калі ў нас быў падказка, ім быў указаны кантракт. Поля і метады класа File. Цяпер у нас нічога няма, мы можам адправіць што заўгодна, нават радок, і гэта прывядзе да дрэннай памылкі.

У той час як канчатковы вынік у абодвух выпадках аднолькавы, што азначае, што код ламаецца, першы стварыў прыемнае паведамленне. Гэта, аднак, вельмі цёмна. Немагчыма даведацца, што гэта за зменная – у нашым выпадку радок – і якія ўласцівасці шукалі і не знайшлі. Адладзіць і выправіць праблему складана. Праграміст павінен адкрыць клас Progress, прачытаць і зразумець яго. Кантракт, у гэтым выпадку, калі вы відавочна не паказваеце падказку тыпу, ёсць defiзнішчаны паводзінамі Прагрэсу. Гэта няяўная дамова, вядомая толькі Прагрэсу. У нашым прыкладзе гэта так defiзавяршаецца доступам да двух палёў, адпраўленага і даўжыні, у метадзе getAsPercent(). У рэальным жыцці кантракт можа быць вельмі складаным, і яго цяжка выявіць, проста паглядзеўшы некалькі секунд на заняткі.

Гэта рашэнне рэкамендуецца толькі ў тым выпадку, калі ні адзін з апісаных ніжэй парад не можа быць лёгка рэалізаваны альбо калі яны ўнясуць сур'ёзныя архітэктурныя змены, якія не патрабуюць высілкаў.

Ercole Palmeri

Інавацыйны бюлетэнь
Не прапусціце самыя важныя навіны пра інавацыі. Падпішыцеся, каб атрымліваць іх па электроннай пошце.

Апошнія артыкулы

Veeam прапануе самую поўную падтрымку праграм-вымагальнікаў - ад абароны да адказу і аднаўлення

Coveware ад Veeam працягне прадастаўляць паслугі рэагавання на інцыдэнты кібервымагальніцтва. Coveware будзе прапаноўваць судова-медыцынскую экспертызу і магчымасці выпраўлення…

Красавік 23 2024

Зялёная і лічбавая рэвалюцыя: як прагнознае тэхнічнае абслугоўванне трансфармуе нафтагазавую прамысловасць

Прагнастычнае тэхнічнае абслугоўванне рэвалюцыянізуе нафтагазавы сектар з інавацыйным і актыўным падыходам да кіравання заводам.…

Красавік 22 2024

Антыманапольны рэгулятар Вялікабрытаніі падымае трывогу BigTech з нагоды GenAI

CMA Вялікабрытаніі выпусціла папярэджанне аб паводзінах Big Tech на рынку штучнага інтэлекту. Там…

Красавік 18 2024

Casa Green: энергетычная рэвалюцыя для ўстойлівай будучыні ў Італіі

Указ «Зялёныя дамы», распрацаваны Еўрапейскім саюзам для павышэння энергаэфектыўнасці будынкаў, завяршыў свой заканадаўчы працэс з...

Красавік 18 2024

Чытайце Innovation на сваёй мове

Інавацыйны бюлетэнь
Не прапусціце самыя важныя навіны пра інавацыі. Падпішыцеся, каб атрымліваць іх па электроннай пошце.

Выконвайце за намі