Programarea orientată pe clasă este atunci când sunt folosite clase care constau numai din metode și proprietăți statice, iar o instanță a clasei nu este niciodată creată. În acest articol voi spune că:
- acest lucru nu oferă niciun avantaj față de programarea procedurală
- nu renunta la obiecte
- prezența membrilor statici ai clasei! = teste de moarte
în funcție de
De obicei, codul depinde de celălalt cod. De exemplu:
Acest cod depinde de variabila $ bar și de funcția substr. $ bar este doar o variabilă locală, definită puțin mai sus în același fișier și în același domeniu. substr este funcția kernel-ului PHP. Aici totul este simplu.
Acum, un astfel de exemplu:
normalizer_normalize este o funcție a pachetului Intl care este integrat în PHP de la versiunea 5.3 și poate fi instalat separat pentru versiunile mai vechi. Aici este ceva mai complicat - codul depinde de disponibilitatea unui anumit pachet.
Acum, această opțiune:
Acesta este un exemplu tipic de programare orientată spre clasă. Foo este strâns legat de baza de date. Și presupunem că clasa de baze de date a fost deja inițializată, iar conexiunea la baza de date (DB) este deja instalată. Se presupune că utilizarea acestui cod va fi:
Foo :: bar implică implicit disponibilitatea bazei de date și starea ei internă. Nu puteți folosi Foo fără o bază de date. dar baza de date. probabil presupune o conexiune la baza de date. Deoarece puteți fi sigur că o conexiune la baza de date a fost deja stabilită, atunci când este chemată Database :: fetchAll. O modalitate arata astfel:
Când apelați Database :: fetchAll. verificăm existența conexiunii apelând metoda de conectare. care, dacă este necesar, primește parametrii de conectare din config. Aceasta înseamnă că baza de date depinde de fișierul config / database.php. Dacă acest fișier nu există, acesta nu poate funcționa. Mergem mai departe. Clasa bazei de date este legată de o bază de date. Dacă trebuie să transferați alți parametri de conectare, va fi cel puțin ușor. Pisica creste. Foo nu depinde numai de disponibilitatea bazei de date. dar depinde și de starea lui. Baza de date depinde de fișierul specific, într-un anumit dosar. Ie implicit clasa Foo depinde de fișierul din folder, deși prin codul său nu este vizibil. În plus, există o grămadă de dependențe de statul global. Fiecare piesă depinde de o altă piesă, care ar trebui să fie în starea corectă și nicăieri nu este indicată în mod clar.
Ceva familiar.
Nu este doar o abordare procedurală? Să încercăm să rescriem acest exemplu într-un stil procedural:
Găsiți 10 diferențe ...
Sugestie: singura diferență este vizibilitatea conexiunii Database :: $ și $ database_connection.
Într-un exemplu orientat spre clasă, conexiunea este disponibilă numai pentru clasa bazei de date în sine. iar în codul procedural această variabilă este globală. Codul are aceleași dependențe, conexiuni, probleme și funcționează la fel. Între $ database_connection și conexiune la baza de date :: $ este aproape nici o diferenta - este doar o sintaxă diferită pentru același lucru, cele două variabile au o stare globală. O patină ușoară a spațiului de nume, datorită utilizării claselor, este cu siguranță mai bună decât nimic, dar nu schimba grav nimic.
orientate spre clasa de programare - este ca achiziționarea unui automobil, să se așeze în ea, periodic deschide și închide ușa, sari pe scaunele, cauzând accidental focul airbag-ului, dar niciodată nu rotiți cheia de contact și nu se clinti. Aceasta este o neînțelegere completă a esenței.
Rotiți cheia de contact
Acum, să încercăm OOP. Să începem cu implementarea lui Foo:
Acum, Foo nu depinde de o anumită bază de date. Atunci când creați o instanță a Foo. trebuie să transferați un obiect care are caracteristicile bazei de date. Aceasta poate fi fie o instanță a bazei de date. și descendența lui. Așadar, putem folosi o altă implementare a bazei de date. care poate primi date de la altundeva. Sau are un strat de cache. Sau este un stub pentru teste, și nu o conexiune reală cu baza de date. Acum trebuie să creați o instanță a bazei de date. acest lucru înseamnă că putem utiliza mai multe conexiuni diferite la diferite baze de date, cu parametri diferiți. Să implementăm baza de date:
Observați cât de simplu a devenit implementarea. În Database :: fetchAll, nu este necesar să verificați starea conexiunii. Pentru a apela baza de date :: fetchAll. trebuie să creați o instanță a clasei. Pentru a crea o instanță a unei clase, trebuie să treceți parametrii de conectare la constructor. Dacă parametrii conexiunii nu sunt valabili sau conexiunea nu poate fi stabilită din alte motive, va fi aruncată o excepție și obiectul nu va fi creat. Acest lucru înseamnă că atunci când apelați Database :: fetchAll. aveți garanția că aveți o conexiune la baza de date. Aceasta înseamnă că Foo trebuie doar să specifice în constructor că are nevoie de baza de date Database $ și va avea o conexiune la baza de date.
Fără o copie a lui Foo. Nu poți apela Foo :: bar. Fără o instanță bazei de date. Nu aveți posibilitatea să instanțiați Foo. Fără parametrii de conectare valabili, nu creați o instanță a bazei de date.
Pur și simplu nu puteți folosi codul dacă nu este îndeplinită cel puțin o condiție.
Comparați acest lucru cu codul orientat pe clasă: puteți să apelați oricând la Foo :: bar, dar va exista o eroare dacă clasa bazei de date nu este gata. Puteți să apelați Database :: fetchAll oricând, dar va exista o eroare dacă există probleme cu fișierul config / database.php. Baza de date :: connect stabilește starea globală pe care depind toate celelalte operații, dar această dependență nu este garantată.
Să ne uităm la asta din partea codului care folosește Foo. Un exemplu procedural:
Puteți scrie această linie oriunde și va fi executată. Comportamentul său depinde de starea globală a conexiunii la baza de date. Deși din cod nu este evident. Să adăugăm tratarea erorilor:
Din cauza dependențelor implicite ale foo_bar. în caz de eroare, va fi dificil să înțelegeți ce anume a rupt.
Pentru comparație, aici este implementarea orientată spre clasă:
Nici o diferență. Efectuarea erorilor este identică, adică este, de asemenea, dificil să găsim sursa problemelor. Acest lucru se datorează faptului că apelarea unei metode statice este o simplă chemare a unei funcții care nu este diferită de orice alt apel al funcției.
PHP va cădea cu o eroare fatală când vine vorba de noul Foo. Am indicat că Foo are nevoie de o instanță de bază de date. dar nu a trecut-o.
PHP va cădea din nou, pentru că Nu am trecut parametrii de conectare la baza de date, pe care am specificat-o în Database :: __ construct.
Acum am satisfăcut toate dependențele care au promis, totul este gata să fie lansat.
Dar să ne imaginăm că parametrii de conectare la baza de date sunt incorecți sau avem unele probleme cu baza de date și conexiunea nu poate fi stabilită. În acest caz, o excepție va fi aruncată atunci când rulează noua bază de date (.). Următoarele linii pur și simplu nu sunt executate. Așadar, nu trebuie să verificăm eroarea după apelul $ foo-> bar () (desigur, puteți verifica dacă sunteți înapoi). Dacă ceva nu merge bine cu oricare dintre dependențe, codul nu va fi executat. Iar excepția aruncată va conține informații utile pentru depanare.
Abordarea orientată spre obiect poate părea mai complicată. În exemplul nostru de cod procedural sau orientat spre clasă, există o singură linie care folosește foo_bar sau Foo :: bar. în timp ce abordarea orientată pe obiecte are trei linii. Este important să înțelegeți esența. Nu am inițializat baza de date în codul procedural, deși trebuie să facem acest lucru în orice caz. Abordarea procedurală necesită prelucrarea erorilor după faptă și în fiecare punct al procesului. Efectuarea erorilor este foarte confuză, pentru că Este dificil să urmăriți care dintre dependențele implicite au provocat eroarea. Codurile hardcode ascund dependențele. Sursele de erori nu sunt evidente. Nu este clar ce depinde codul dvs. pentru funcționarea normală.
Abordarea orientată spre obiect face ca toate dependențele să fie explicite și evidente. Foo necesită o instanță a bazei de date. iar instanța bazei de date are nevoie de parametrii de conectare.
În abordarea procedurală, responsabilitatea revine funcțiilor. Apelăm metoda Foo :: bar - acum trebuie să ne întoarcem rezultatul. Această metodă, la rândul ei, deleagă sarcina Database :: fetchAll. Acum, el este deja pe deplin responsabil și încearcă să se conecteze la baza de date și să returneze unele date. Și dacă ceva nu merge în vreun moment ... cine știe ce se va întoarce la tine și unde.
Abordarea orientată pe obiecte schimbă o parte din responsabilitate către codul de apel și aceasta este forța sa. Doriți să sunați la Foo :: bar. Ei bine, atunci dați-i o legătură cu baza de date. Ce legătură? Nu contează dacă este o instanță a bazei de date. Aceasta este forța pentru introducerea dependențelor. Ea face ca dependențele necesare să fie explicite.
În codul procedural, creați o mulțime de dependențe tari și încurcați diferite părți ale codului cu sârmă de oțel. Totul depinde de tot. Creați o bucată de software monolit. Nu vreau să spun că nu va funcționa. Vreau să spun că acesta este un design foarte rigid, care este foarte dificil de dezasamblat. Pentru aplicații mici, acest lucru poate funcționa bine. Pentru mari se transformă într-o groază de complicații, care nu pot fi testate, extinse și depanate:
În codul orientat pe obiecte, cu introducerea dependențelor, creați multe blocuri mici, fiecare fiind independentă. Fiecare bloc are o interfață bine definită pe care alte blocuri o pot folosi. Fiecare bloc știe că are nevoie de alții pentru ca totul să funcționeze. În codul procedural și orientat spre clasă, îi asociați pe Foo cu baza de date exact la momentul scrierii codului. În codul orientat pe obiecte, specificați că Foo are nevoie de o bază de date. dar lasă loc de manevră, ce poate fi. Când doriți să utilizați Foo. Va trebui să legați o instanță specifică de Foo la o instanță specifică a bazei de date:
Abordarea orientată spre clasă arată în mod înșelător de simplă, dar unghiile devin greu de codat cu unghiile dependențelor. Abordarea orientată spre obiect lasă totul flexibil și izolat până la utilizare, care poate părea mai complexă, dar este mai ușor de gestionat.
Membrii statici
De ce avem nevoie de proprietăți și metode statice? Ele sunt utile pentru datele statice. De exemplu, datele de care depinde instanța, dar care nu se schimbă niciodată. Un exemplu complet ipotetic:
Să presupunem că această clasă ar trebui să lege tipurile de date din baza de date la tipurile interne. Aceasta necesită o hartă de tip. Această hartă este întotdeauna aceeași pentru toate instanțele bazei de date și este utilizată în mai multe metode de baze de date. De ce să nu faceți o hartă a unei proprietăți statice? Datele nu sunt niciodată schimbate, ci doar citite. Și asta va salva o mică memorie, pentru că Datele distribuite între toate instanțele bazei de date. pentru că accesul la date are loc numai în cadrul clasei, ceea ce nu va crea dependențe externe. Proprietățile statice nu ar trebui să fie niciodată accesibile din exterior, deoarece acestea sunt doar variabile globale. Și am văzut deja ce duce la asta ...
De asemenea, proprietățile statice pot fi utile în cache-ul unor date care sunt identice pentru toate instanțele. Proprietățile statice există, în cea mai mare parte, ca o tehnică de optimizare, ele nu ar trebui considerate filozofie de programare. Și metodele statice sunt utile ca metode auxiliare și constructori alternativi.
Problema metodelor statice este că ele creează o dependență rigidă. Când apelați Foo :: bar (). această linie de cod devine asociată cu o anumită clasă Foo. Acest lucru poate duce la probleme.
Utilizarea metodelor statice este permisă în următoarele situații:
- Se garantează existența dependenței. În cazul în care apelul este intern sau dependența face parte din mediul înconjurător. De exemplu:
Aici baza de date depinde de clasa specifică - DOP. Dar PDO face parte din platformă, este o clasă pentru lucrul cu baza de date oferită de PHP. În orice caz, pentru a lucra cu baza de date va trebui să utilizeze un fel de API.
Această funcție mică de ajutor oferă pur și simplu un wrapper pentru un anumit algoritm care ajută la calcularea unui număr bun pentru argumentul $ k. care este utilizat în constructor. pentru că trebuie să fie chemată înainte de a crea o instanță a clasei, trebuie să fie statică. Acest algoritm nu are dependențe externe și este puțin probabil să fie înlocuit. Se folosește astfel:
Acest lucru nu creează dependențe suplimentare. Clasa depinde de sine.
În ambele cazuri, rezultatul este o instanță a DateTime și, în ambele cazuri, codul este legat oricum de clasa DateTime. Metoda statică DateTime :: createFromFormat este un constructor obiect alternativ care returnează același lucru ca noul DateTime. dar folosind funcționalități suplimentare. Unde puteți scrie o nouă clasă. puteți scrie și metoda Class :: (). Nu există noi dependențe în acest caz.
Alte opțiuni pentru utilizarea metodelor statice afectează legarea și pot forma dependințe implicite.
Un cuvânt despre abstractizare
De ce toată această agitație cu dependențe? Abilitatea de a rezuma! Odată cu creșterea produsului dvs., complexitatea acestuia crește. Și abstractizarea este cheia pentru gestionarea complexității.
De exemplu, aveți clasa de aplicații. care reprezintă cererea dumneavoastră. El comunică cu utilizatorul de clasă. care este o prezentare a utilizatorului. Care primește date din baza de date. Clasa bazei de date necesită DatabaseDriver. DatabaseDriver are nevoie de parametrii de conectare. Și așa mai departe. Dacă apelați doar Application :: Start () static, ceea ce va determina utilizatorul :: getData () static, ceea ce va duce la o bază de date statice, și așa mai departe, în speranța că fiecare strat se va ocupa de dependențele lor, puteți obține o mizerie groaznic dacă ceva nu merge nu așa. Este imposibil să ghici dacă apelul Aplicație :: start () va funcționa. deoarece nu este deloc evident modul în care se vor comporta dependențele interne. Chiar mai rău este că singura modalitate de a influența comportamentul aplicației :: start () - este de a modifica codul sursă pentru această clasă și codul de clasă pe care el vyzyzvaet clase și un cod care vyzyzvayut acele clase ... în casa pe care Jack a construit.
Abordarea cea mai eficientă atunci când se creează aplicații complexe este crearea de părți separate, care se pot baza pe viitor. Părți la care vă puteți opri să vă gândiți, în care puteți fi siguri. De exemplu, dacă apelați static Database :: fetchAll (.). Nu există nicio garanție că conexiunea la baza de date este deja instalată sau va fi instalată.
Dacă codul din interiorul acestei funcții este executat, înseamnă că instanța de bază de date a fost transferată cu succes, ceea ce înseamnă că instanța obiectului Database a fost creată cu succes. Dacă clasa bazei de date este proiectată corect, puteți fi siguri că prezența unei instanțe din această clasă înseamnă capacitatea de a executa interogări în baza de date. Dacă nu există nici o instanță a clasei, corpul funcției nu va fi executat. Acest lucru înseamnă că funcția nu ar trebui să aibă grijă de starea bazei de date, clasa de baze de date o va face ea însăși. Această abordare vă permite să uitați de dependențe și să vă concentrați asupra rezolvării problemelor.
Fără posibilitatea de a nu se gândi la dependențele și dependențele acestor dependențe, este aproape imposibil să scriem cel puțin o aplicație complicată. Baza de date poate fi o clasă înveliș mici sau monstru multistrat imens cu o mulțime de dependențe, poate începe ca un înveliș mic și suferi mutații, odată ce acest monstru gigant cu vremurile, puteți moșteni o bază de date de clasă și să treacă într-un descendent funcție, nu este important pentru funcția (baza de date $ bază de date). atâta timp cât interfața publică a bazei de date nu se schimbă. Dacă clasele dvs. sunt separate în mod corespunzător de restul aplicației prin implementarea dependențelor, puteți testa fiecare dintre ele folosind stubs în locul dependențelor lor. Când testat clasa suficient pentru a vă asigura că funcționează așa cum ar trebui, puteți arunca excesul de cap, doar știind ce să lucreze cu baza de date trebuie să utilizați baza de date instanță.
Programarea orientată spre clasă este prostia. Aflați cum să utilizați OOP.