Există o mulțime de articole care explică ce modele de design sunt și cum să le implementeze; webul nu are nevoie de încă unul din aceste articole! În schimb, în acest articol, vom discuta mai mult cand și De ce, mai degrabă decât care și Cum.
Voi prezenta diferite situații și cazuri de utilizare pentru modele și va oferi, de asemenea, definiții scurte pentru a ajuta pe aceia dintre voi care nu sunt atât de familiarizați cu aceste modele specifice. Să începem.
Tutorial publicatLa fiecare câteva săptămâni, revizuim câteva postări preferate ale cititorului nostru de-a lungul istoriei site-ului. Acest tutorial a fost publicat pentru prima oară în octombrie 2012.
Acest articol acoperă unele dintre cele diferite Agile modele de design, documentate în cărțile lui Robert C. Martin. Aceste modele sunt adaptări moderne ale modelelor de design originale definite și documentate de Gangul celor Patru în 1994. Modelele lui Martin prezintă o abordare mai recentă a modelelor GoF și funcționează mai bine cu tehnicile și problemele moderne de programare. De fapt, aproximativ 15% din modelele originale au fost înlocuite cu modele mai noi, iar modelele rămase au fost ușor modernizate.
Modelul fabricii a fost inventat pentru a ajuta programatorii să organizeze informațiile legate de crearea obiectelor. Obiectele au uneori mulți parametri constructori; alteori, ele trebuie să fie populate cu informații prestabilite imediat după crearea lor. Aceste obiecte ar trebui create în fabrici, păstrând toate informațiile referitoare la crearea și inițierea acestora într-un singur loc.
Cand Utilizați un model de fabrică atunci când vă aflați scriind codul pentru a aduna informațiile necesare pentru a crea obiecte.
De ce: Uzinele ajută la limitarea logicii creării de obiecte într-un singur loc. De asemenea, ele pot rupe dependențele pentru a facilita cuplarea în vrac și injecția de dependență pentru a permite o testare mai bună.
Există două modele frecvent utilizate pentru a prelua informații dintr-un strat de persistență sau dintr-o sursă externă de date.
Acest model definește un canal de comunicare între o soluție de persistență și logica de afaceri. Pentru aplicații mai simple, poate să recupereze sau să recreeze obiecte întregi de la sine, dar crearea de obiecte este responsabilitatea fabricilor în cele mai complexe aplicații. Poarta pur și simplu prelua și persistă date brute.
Cand: Când trebuie să recuperați sau să persistați informații.
De ce: Oferă o interfață publică simplă pentru operații complicate de persistență. De asemenea, încapsulează cunoștințele de persistență și decuplează logica de afaceri de logica persistenței.
De fapt, modelul de gateway este doar o implementare particulară a unui alt model de design pe care îl vom discuta în scurt timp: modelul adaptorului.
Există momente când nu puteți (sau nu doriți) să expuneți cunoștințele stratului de persistență la clasele de afaceri. Modelul proxy este o modalitate bună de a vă păcăli clasele de afaceri în a gândi că folosesc obiecte deja existente.
Cand: Trebuie să preluați informații dintr-un strat de persistență sau dintr-o sursă externă, dar nu doriți ca logica dvs. de afaceri să știe acest lucru.
De ce: Pentru a oferi o abordare non-intruzivă pentru a crea obiecte în spatele scenei. Se deschide, de asemenea, posibilitatea de a prelua aceste obiecte în zbor, leneși și din diferite surse.
Un proxy implementează în mod eficient aceeași interfață ca un obiect real și îi imită funcționalitatea. Logica de afaceri o folosește pur și simplu ca și cum ar fi un obiect real, dar, de fapt, proxy-ul creează obiectul dacă nu există.
Modelul de obiect activ a jucat, de asemenea, o contribuție la sistemele inițiale multi-tasking.
Bine bine. Acest lucru minunat și toate, dar cum putem găsi obiectele de care avem nevoie pentru a crea?
Modelul repozitoriu este foarte util pentru implementarea metodelor de căutare și limbajelor de interogare mini-interogare. Este nevoie de aceste interogări și utilizează un gateway pentru a obține datele pentru ca o fabrică să producă obiectele de care aveți nevoie.
Modelul repozitoriu este diferit de celelalte modele; există ca parte a Domain Driven Design (DDD) și nu este inclusă în cartea lui Robert C. Martin.
Cand: Trebuie să creați mai multe obiecte pe baza criteriilor de căutare sau când trebuie să salvați mai multe obiecte în stratul de persistență.
De ce: Să permiteți clienților care au nevoie de obiecte specifice să lucreze cu o limbă comună și bine izolată de interogare și persistență. Elimină încă un cod legat de creație din logica afacerii.
Dar dacă repozitoriul nu poate găsi obiectele? O opțiune ar fi returnarea a NUL
valoare, dar acest lucru are două efecte secundare:
dacă (is_null ($ param)) retur;
) în codul dvs..O abordare mai bună este de a reveni a nul
obiect.
Un obiect nul implementează aceeași interfață a celorlalte obiecte, dar membrii obiectului returnează o valoare neutră. De exemplu, o metodă care returnează un șir va returna un șir gol; un alt membru care returnează o valoare numerică ar reveni la zero. Acest lucru vă forțează să implementați metode care nu returnează date semnificative, dar puteți utiliza aceste obiecte fără să vă faceți griji în legătură cu refuzarea cu moștenire sau cu ștergerea codului cu cecuri nul.
Cand: Verificați frecvent pentru nul
sau ați refuzat moștenirile.
De ce: Acesta poate adăuga claritate codului dvs. și vă obligă să vă gândiți mai mult la comportamentul obiectelor dvs..
Nu este neobișnuit să apelați mai multe metode pe un obiect înainte de a-și putea face treaba. Există situații în care trebuie să pregătiți un obiect după crearea sa, înainte de a-l folosi cu adevărat. Acest lucru duce la duplicarea codului atunci când creați acele obiecte în locuri diferite.
Cand: Când trebuie să efectuați mai multe operații pentru a pregăti obiecte pentru utilizare.
De ce: Pentru a muta complexitatea de la codul consumator la codul de creare.
Sună bine, nu-i așa? De fapt, este foarte util în multe situații. Modelul de comandă este utilizat pe scară largă pentru implementarea tranzacțiilor. Dacă adăugați un simplu Anula()
la un obiect de comandă, poate urmări toate tranzacțiile anulate pe care le-a efectuat și le poate inversa dacă este necesar.
Deci, acum aveți zece (sau mai multe) obiecte de comandă și doriți să fie difuzate simultan. Le puteți aduna într-un obiect activ.
Obiectul activ simplu și interesant are o singură responsabilitate: păstrați o listă de obiecte de comandă și executați-le.
Cand: Mai multe obiecte similare trebuie executate cu o singură comandă.
De ce: Își obligă clienții să efectueze o singură sarcină și să afecteze mai multe obiecte.
Un obiect activ elimină fiecare comandă din lista sa după executarea comenzii; ceea ce înseamnă că puteți executa comanda o singură dată. Unele exemple din lumea reală a unui obiect activ sunt:
Modelele de design sunt aici pentru a rezolva problemele.
a cumpara()
comandă pe fiecare produs le elimină din cărucior.Modelul de obiect activ a jucat, de asemenea, o contribuție la sistemele inițiale multi-tasking. Fiecare obiect din interiorul unui obiect activ va păstra o referință la obiectul activ. Ei ar executa o parte din munca lor și apoi s-au readus în coadă. Chiar și în sistemele de astăzi, puteți utiliza un obiect activ pentru a lăsa alte obiecte să funcționeze în timp ce așteptați un răspuns de la o altă aplicație.
Sunt sigur că ați auzit promisiunea mare de programare orientată obiect: reutilizarea codului. Începătorii adoptivi ai POR au prevăzut utilizarea bibliotecilor și claselor universale în milioane de proiecte diferite. Nu sa întâmplat niciodată.
Acest model permite reutilizarea parțială a codului. Este practică cu mai mulți algoritmi care diferă ușor între ei.
Cand: Eliminați duplicarea într-un mod simplu.
De ce: Există dublarea și flexibilitatea nu este o problemă.
Dar flexibilitatea este drăguță. Dacă am nevoie de ea?
Cand: Flexibilitatea și reutilizarea sunt mai importante decât simplitatea.
De ce: Utilizați-l pentru a implementa bucăți mari, interschimbabile de logică complicată, menținând în același timp o semnătură comună de algoritmi.
De exemplu, puteți crea o generică Calculator
și apoi utilizați diferite ComputationStrategy
obiecte pentru efectuarea calculelor. Acesta este un model moderat utilizat și este cel mai puternic atunci când trebuie să definiți multe comportamente condiționale.
Odată cu creșterea numărului de proiecte, devine tot mai dificilă accesul utilizatorilor externi la aplicația noastră. Acesta este un motiv pentru a oferi un punct de intrare bine definit pentru aplicația sau modul în cauză. Alte astfel de motive pot include dorința de a ascunde funcționarea și structura internă a modulului.
O fatadă este, în esență, un API - o interfață frumoasă și orientată spre client. Atunci când un client solicită una dintre aceste metode frumoase, fațada deleagă o serie de apeluri la clasele pe care le ascunde pentru a le oferi clientului informațiile necesare sau rezultatul dorit.
Cand: Pentru a vă simplifica API-ul sau pentru a ascunde intenționat logica internă de afaceri.
De ce: Puteți controla API-ul și implementările reale și logica în mod independent.
Controlul este bun și de multe ori trebuie să faci o sarcină când ceva se schimbă. Utilizatorii trebuie să fie avertizați, LED-urile roșii trebuie să clipească, o alarmă trebuie să sune ... ai idee.
Cadrul Laravel popular face o utilizare excelentă a modelului de fațadă.
Un obiect nul implementează aceeași interfață ca celelalte obiecte.
Modelul de observator oferă o modalitate ușoară de a monitoriza obiectele și de a lua măsuri atunci când condițiile se schimbă. Există două tipuri de implementări ale observatorilor:
Cand: Pentru a oferi un sistem de notificare în interiorul logicii dvs. de afaceri sau în lumea exterioară.
De ce: Modelul oferă o modalitate de comunicare a evenimentelor cu orice număr de obiecte diferite.
Cazurile de utilizare pentru acest model sunt notificări prin e-mail, daemoane de logare sau sisteme de mesagerie. Desigur, în viața reală, există nenumărate alte modalități de ao folosi.
Modelul observatorului poate fi extins cu un model de mediator. Acest model ia două obiecte ca parametri. Mediatorul subscrie la primul parametru, iar atunci când se întâmplă o schimbare cu obiectul observat, mediatorul decide ce trebuie să facă cu cel de-al doilea obiect.
Cand: Obiectele afectate nu pot ști despre obiectele observate.
De ce: Pentru a oferi un mecanism ascuns de a afecta alte obiecte din sistem atunci când un obiect se schimbă.
Uneori, aveți nevoie de obiecte speciale care sunt unice în aplicația dvs. și doriți să vă asigurați că toți consumatorii pot vedea orice schimbare făcută acestor obiecte. De asemenea, doriți să împiedicați crearea de instanțe multiple de astfel de obiecte din anumite motive, cum ar fi timpul de inițiere lungă sau probleme cu acțiuni concurate la unele biblioteci terțe părți.
Un singleton este un obiect care are un constructor privat și un public getInstance ()
metodă. Această metodă asigură existența unei singure instanțe a obiectului.
Cand: Trebuie să obțineți singularitate și să doriți o platformă transversală, soluție lazy evaluată, care oferă, de asemenea, posibilitatea creării prin derivare.
De ce: Pentru a oferi un singur punct de acces atunci când este necesar.
O altă abordare a singularității este modelul de design monostat. Această soluție utilizează un truc oferit de limbile de programare orientate pe obiecte. Are metode publice dinamice care obțin sau stabilesc valorile variabilelor private statice. Aceasta, la rândul său, asigură că toate instanțele acestor clase împărtășesc aceleași valori.
Cand: Transparența, derivabilitatea și polimorfismul sunt preferate împreună cu singularitatea.
De ce: Pentru a ascunde de la utilizatori / clienți faptul că obiectul oferă singularitate.
Acordați o atenție deosebită singularității. El poluează spațiul de nume global și, în cele mai multe cazuri, poate fi înlocuit cu ceva mai potrivit pentru această situație.
Modelul repozitoriu este destul de util pentru implementarea metodelor de căutare ...
Deci aveți un întrerupător și o lumină. Comutatorul poate activa sau dezactiva lumina, dar acum ați cumpărat un ventilator și doriți să utilizați comutatorul vechi cu acesta. Acest lucru este ușor de realizat în lumea fizică; luați comutatorul, conectați firele și violați.
Din păcate, nu este atât de ușor în lumea programării. Tu ai Intrerupator
clasa și a Ușoară
clasă. Dacă ale tale Intrerupator
utilizează Ușoară
, cum ar putea folosi Ventilator
?
Uşor! Copiați și lipiți Intrerupator
, și schimbați-l pentru a utiliza Ventilator
. Dar asta este duplicarea codului; este echivalentul cumpărării unui alt switch pentru ventilator. Ai putea prelungi Intrerupator
la FanSwitch
, și folosiți acest obiect în schimb. Dar dacă vrei să folosești a Buton
sau Telecomandă
, în loc de a Intrerupator
?
Acesta este cel mai simplu model inventat vreodată. Utilizează doar o interfață. Asta este, dar există mai multe implementări diferite.
Cand: Trebuie să conectați obiecte și să mențineți flexibilitatea.
De ce: Pentru că este cel mai simplu mod de a obține flexibilitate, respectând atât principiul inversiunii dependenței, cât și principiul închiderii deschise.
PHP este tastat dinamic. Acest lucru înseamnă că puteți omite interfețe și puteți folosi obiecte diferite în același context - riscând ca o moștenire refuzată. Cu toate acestea, PHP permite, de asemenea, definirea interfețelor și vă recomandăm să utilizați această mare funcționalitate pentru a oferi claritate intenției codului dvs. sursă.
Dar ai deja o grămadă de clase cu care vrei să vorbești? Da, desigur. Există multe biblioteci, API-uri terțe și alte module cu care trebuie să vorbești, dar acest lucru nu înseamnă că logica noastră de afaceri trebuie să cunoască detaliile acestor lucruri.
Modelul adaptorului creează pur și simplu o corespondență între logica de afaceri și altceva. Am văzut deja un astfel de model în acțiune: modelul de poartă.
Cand: Trebuie să creați o conexiune cu un modul, o bibliotecă sau un API care există deja și care se schimbă.
De ce: Pentru a permite logicii dvs. de afaceri să se bazeze numai pe metodele publice pe care le oferă adaptorul și să permită schimbarea ușoară a cealaltă parte a adaptorului.
Dacă oricare dintre modelele de mai sus nu se potrivește cu situația dvs., atunci puteți folosi ...
Acesta este un model foarte complicat. Eu personal nu-mi place, deoarece de obicei este mai ușor să adopți o abordare diferită. Dar pentru acele cazuri speciale, când alte soluții nu reușesc, puteți lua în considerare modelul de pod.
Cand: Modelul adaptorului nu este suficient și schimbați clasele de pe ambele părți ale țevii.
De ce: Oferirea unei flexibilități sporite la costul unei complexități semnificative.
Luați în considerare faptul că aveți un script cu comenzi similare și doriți să efectuați un singur apel pentru a le rula. Aștepta! N-am văzut deja ceva de genul asta mai devreme? Modelul obiectului activ?
Da, da, am făcut-o. Dar acesta este un pic diferit. Este modelul compozit și, ca modelul obiectului activ, păstrează o listă de obiecte. Apelarea unei metode pe un obiect compozit numește aceeași metodă pentru toate obiectele sale, fără a le elimina din listă. Clienții care apelează la o metodă se gândesc că vorbesc cu un singur obiect al acelui tip particular, dar, de fapt, acțiunile lor sunt aplicate multor obiecte de același tip.
Cand: Trebuie să aplicați o acțiune asupra mai multor obiecte similare.
De ce: Pentru a reduce duplicarea și a simplifica modul în care sunt numite obiecte similare.
Iată un exemplu: aveți o aplicație capabilă să creați și să plasați Comenzi
. Să presupunem că aveți trei ordine: $ order1
, $ order2
și $ order3
. Ai putea suna loc()
pe fiecare dintre ele, sau ar putea să conțină aceste ordine într-un $ compositeOrder
obiect, și numiți-l loc()
metodă. Acest lucru, la rândul său, îl cheamă loc()
pe toate conținute Ordin
obiecte.
Gateway-urile recuperează și persistă numai date brute.
O mașină de stat finită (FSM) este un model care are un număr finit de stări discrete. Implementarea unui FSM poate fi dificilă, iar cel mai simplu mod de a face acest lucru îl implică pe cei încrezători intrerupator
afirmație. Fiecare caz
instrucțiunea reprezintă o stare curentă în mașină și știe cum să activeze următoarea stare.
Dar cu toții știm asta comuta ... caz
afirmațiile sunt mai puțin de dorit pentru că ele produc un fan-out nedorit ridicat pe obiectele noastre. Așa că uitați comuta ... caz
declarație, și să ia în considerare modelul de stat. Modelul de stat este compus din mai multe obiecte: un obiect de coordonare a lucrurilor, o interfață reprezentând o stare abstractă și apoi mai multe implementări - câte unul pentru fiecare stat. Fiecare stat știe care stare vine după ea, iar statul poate anunța obiectul coordonator să își stabilească noua stare la următoarea linie.
Cand: Este necesară implementarea logicii asemănătoare FSM.
De ce: Pentru a elimina problemele unui a comuta ... caz
și pentru a încapsula mai bine semnificația fiecărui stat în parte.
Un distribuitor de alimente ar putea avea a principal
clasa care are o referință la o stat
clasă. Posibile clase de stat ar putea fi ceva de genul: WaitingForCoin
, InsertedCoin
, SelectedProduct
, WaitingForConfirmation
, DeliveringProduct
, ReturningChange
. Fiecare stat își îndeplinește sarcina și creează următorul obiect de stat pentru a trimite la clasa coordonatorului.
Există momente în care implementați clase sau module în cadrul unei aplicații și nu le puteți modifica fără a afecta radical sistemul. Dar, în același timp, trebuie să adăugați noi funcționalități pe care utilizatorii dvs. le solicită.
Modelul decoratorului poate ajuta în aceste situații. Este foarte simplu: luați funcționalitatea existentă și adăugați-o. Acest lucru se realizează prin extinderea clasei originale și prin furnizarea de noi funcționalități la timpul de execuție. Clientii vechi continua sa foloseasca noul obiect asa cum ar fi unul vechi, iar noii clienti vor folosi atat functiile vechi cat si cele noi.
Cand: Nu puteți schimba clasele vechi, dar trebuie să implementați un nou comportament sau stat.
De ce: Oferă o modalitate neinstruiată de a adăuga noi funcționalități.
Un exemplu simplu este imprimarea datelor. Imprimați câteva informații utilizatorului ca text simplu, dar doriți, de asemenea, să oferiți posibilitatea imprimării în HTML. Modelul de decorator este o soluție care vă permite să păstrați ambele funcționalități.
Dacă problema dvs. de extindere a funcționalității este diferită - să zicem, aveți o structură complexă de obiecte și doriți să adăugați funcționalitate la mai multe noduri simultan - nu este posibilă o simplă iterație, dar un vizitator ar putea fi o soluție viabilă. Dezavantajul, totuși, este că o implementare a tiparului unui vizitator necesită o modificare a clasei vechi, dacă nu a fost concepută pentru a accepta un vizitator.
Cand: Un decorator nu este potrivit și este admisă o complexitate suplimentară.
De ce: Pentru a permite o abordare organizată pentru definirea funcționalității pentru mai multe obiecte, dar cu prețul complexității ridicate.
Utilizați modele de design pentru a vă rezolva problemele, dar numai dacă se potrivesc.
Modelele de proiectare ajută la rezolvarea problemelor. Ca o recomandare de implementare, nu vă numiți niciodată clasele după modele. În schimb, găsiți numele potrivite pentru abstracțiile corecte. Acest lucru vă ajută să vă discerneți mai bine atunci când aveți cu adevărat nevoie de un model, spre deosebire de implementarea unei singure, pentru că puteți.
Unii pot spune că, dacă nu vă numiți clasa cu numele modelului în el, atunci alți dezvoltatori vor avea dificultăți în înțelegerea codului. Dacă este greu să recunoști un model, atunci problema este în implementarea modelului.
Utilizați modele de design pentru a vă rezolva problemele, dar numai dacă se potrivesc. Nu le abuza. Veți găsi că o soluție mai simplă se potrivește unei mici probleme; în timp ce veți descoperi că aveți nevoie de un model numai după ce implementați câteva alte soluții.
Dacă sunteți nou în a proiecta modele, sper că acest articol ți-a dat o idee despre modul în care modelele pot fi utile în aplicațiile tale. Vă mulțumim pentru lectură!