Unul dintre faptele neschimbate ale vieții este că schimbarea este constanta nesfârșită în fiecare ciclu de viață al software-ului - unul din care nu puteți fugi. Provocarea este de a se adapta la această schimbare cu o latență minimă și flexibilitate maximă.
Vestea bună este că probabil că cineva ți-a rezolvat deja problemele de proiectare, iar soluțiile lor au evoluat în cele mai bune practici; aceste bune practici convenite sunt denumite "modele de design". Astăzi, vom explora două modele populare de design și vom afla cum designul bun vă poate ajuta să scrieți un cod care este curat și extensibil.
Să presupunem că aveți un sistem de moștenire existent. Acum aveți sarcina de a face ca lucrul să funcționeze cu o nouă bibliotecă terță parte, dar această bibliotecă are un API diferit față de ultima pe care o utilizați. Sistemul vechi așteaptă acum o interfață diferită de ceea ce oferă noua bibliotecă. Bineînțeles, ați putea fi curajoși (citiți, nebuni) suficient pentru a vă gândi la schimbarea codului vechi pentru a vă adapta la noua interfață, dar la fel ca la fiecare sistem vechi - niciodată.
Adaptoare la salvare! Pur și simplu scrie un adaptor
(o nouă clasă de împachetare) între sistemele care asculta cererile clienților la interfața mai veche și redirecționează sau le traduce în apeluri către noua interfață. Această conversie poate fi implementată fie cu moștenire, fie cu o compoziție.
Designul excelent nu este doar despre reutilizabilitate, ci despre extensibilitate.
Adaptorii ajută clase incompatibile să colaboreze prin a lua o interfață și să o adapteze la una pe care clientul o poate analiza.
Destul de chit-chat; Să mergem la afaceri, nu? Sistemul nostru de software vechi utilizează următoarele LegacyVideoController
interfață pentru controlul sistemului video.
interfață publică LegacyVideoController Începe redarea după startTimeTicks * de la începutul videoclipului * @param startTimeTicks timp în milisecunde * / public void startPlayback (long startTimeTicks); ...
Codul clientului care utilizează acest controler arată astfel:
void public playBackVideo (timpToStart, controller LegacyVideoController) if (controller! = null) controller.startPlayback (timeToStart);
Nu este nimic nou aici, într-adevăr - se întâmplă destul de frecvent. Cerințele utilizatorilor sunt predispuse să se schimbe tot timpul, iar sistemul nostru vechi trebuie acum să funcționeze cu un nou controler video, având următoarea interfață:
interfață publică AdvancedVideoController / ** * localizează capul controlerului după timp * de la începutul pistei * timpul @param time definește numărul de căutări necesare * / public void seek (Time time); / ** * Redă piesa * / void public play ();
Ca urmare, codul clientului se rupe, deoarece această nouă interfață nu este compatibilă.
Deci, cum să ne ocupăm de această interfață modificată fără a schimba codul nostru vechi? Știi răspunsul acum, nu-i așa? Scriem un adaptor simplu pentru a modifica interfața sa pentru a se adapta la cea existentă, după cum urmează:
public class AdvancedVideoControllerAdapter implementează LegacyVideoController private AdvancedVideoController advancedVideoController; publicul AdvancedVideoControllerAdapter (AdvancedVideoController advancedVideoController) this.advancedVideoController = advancedVideoController; @Override public void startPlayback (lung startTimeTicks) // Convertiți lung în TimeTime Time startTime = getTime (startTimeTicks); // Adaptați avansateVideoController.seek (startTime); advancedVideoController.play ();
Acest adaptor implementează interfața țintă, pe care clientul o așteaptă, astfel încât nu este nevoie să modificați codul clientului. Componem adaptorul cu o instanță a interfeței de adaptare.
Această relație "a-a" permite adaptorului să delege solicitarea clientului la instanța reală.
Adaptorii ajută, de asemenea, la decuplarea codului clientului și la implementarea acestuia.
Putem acum să înfășurăm noul obiect din acest adaptor și să facem acest lucru fără a face modificări în codul clientului, deoarece obiectul nou este acum convertit / adaptat la aceeași interfață.
AdvancedVideoController avansatăController = controllerFactory.createController (); // adaptați LegacyVideoController controllerAdapter = nou AdvancedVideoControllerAdapter (advancedController); playBackVideo (20, controllerAdapter);
Un adaptor poate fi o trecere simplă sau poate fi suficient de inteligent pentru a furniza unele add-on-uri, în funcție de complexitatea interfeței care trebuie suportată. În mod similar, un adaptor poate fi utilizat pentru a înfășura mai multe obiecte dacă interfața țintă este complexă și noua funcționalitate a fost împărțită în două sau mai multe clase.
Deși există multe modele care se ocupă cu crearea obiectelor, un model specific se evidențiază. Astăzi vom inspecta unul dintre cele mai simple, dar neînțelese: modelul Singleton.
După cum sugerează și numele, scopul singleton este de a crea o singură instanță a clasei și de a oferi acces global la ea. Exemplele pot fi un nivel de aplicare ascunzătoare
, un grup de obiecte de fire, conexiuni etc. Pentru astfel de entități, singura instanță trebuie să fie suficientă, altfel amenință stabilitatea și învinge scopul aplicației.
O implementare cu oase goale în Java ar arata ca:
public class ApplicationCache hartă privatăattributeMap; // instanță statică privată Static ApplicationCache instanță; // Static accesor method public static ApplicationCache getInstance () if (instanță == null) instance == new ApplicationCache (); retur; // private Constructor private ApplicationCache () attributeMap = createCache (); // Initializeaza cache-ul
În exemplul nostru, clasa deține un membru static de același tip ca cel al clasei, care este accesat printr-o metodă statică. Noi facem uz de Inițierea leneșilor aici, întârziind inițializarea cache-ului, până când este efectiv necesar în timpul rulării. Constructorul este, de asemenea, făcut privat, astfel încât o nouă instanță a acestei clase să nu poată fi creată utilizând nou
operator. Pentru a prelua memoria cache, invocăm:
ApplicationCache cache = ApplicationCache.getInstance (); // utilizați cache pentru a îmbunătăți performanța
Funcționează perfect, atâta timp cât avem de-a face cu un model cu un singur filet. Dar viața, așa cum o știm, nu este atât de simplă. Într-un mediu cu mai multe fire, trebuie să sincronizați inițializarea leneș sau să o eliminați, creând memoria cache de îndată ce se încarcă clasa, fie prin utilizarea blocurilor statice, fie prin inițializarea în timp ce se declară cache-ul.
Sincronizăm inițializarea leneșă pentru a ne asigura că codul de inițializare este rulat o singură dată. Acest cod funcționează cu versiunea Java 5.0 și mai mult datorită idiosincrasiilor asociate implementării sincronizate
și volatil
în Java.
public class ApplicationCache hartă privatăattributeMap; // volatile astfel încât JVM out-of-order scrie nu se întâmplă privat static volatilă ApplicationCache exemplu; public Static AplicațieCache getInstance () // Verificat o dată dacă (exemplu == null) // Sincronizat la blocarea la nivel de clasă sincronizat (ApplicationCache.class) // Checked din nou dacă (instance == null) instance == new ApplicationCache (); retur; aplicație privată ApplicationCache () attributeMap = createCache (); // Initializeaza cache-ul
Noi facem variabila de instanță volatilă astfel încât JVM să împiedice scrierea în afara ordinului pentru aceasta. De asemenea, efectuăm un control dublu (de aici numele), de exemplu, în timpul sincronizării inițializării, astfel încât orice secvență de 2 sau mai multe fire să nu corupe statul sau să ducă la crearea a mai mult de o instanță a cache-ului. S-ar fi putut sincroniza întreaga metodă de acces static, dar ar fi fost o depășire, deoarece sincronizarea este necesară doar până când obiectul este complet inițializat; niciodată din nou în timp ce îl accesați.
O modalitate mai ușoară ar fi să elimini avantajele inițializării lenece, rezultând și un cod mai curat:
public class ApplicationCache hartă privatăattributeMap; // inițializat în timp ce declarația privată statică ApplicationCache instanță = new ApplicationCache (); statică publică ApplicationCache getInstance () instanță retur; / / private Constructor private ApplicationCache () attributeMap = createCache (); // Initializeaza cache-ul
Imediat ce încărcarea clasei și variabilele sunt inițializate, invocăm constructorul privat pentru a crea singura instanță a cache-ului. Pierdem beneficiile inițializării inițiale a instanței, dar codul este mult mai curat. Ambele metode sunt sigure pentru fir și puteți alege unul potrivit pentru mediul dvs. de proiect.
În funcție de cerințele dvs., ați putea dori, de asemenea, să vă protejați împotriva:
readResolve ()
metoda de la Serialization APITitlul tutorialului nostru este puțin înșelător, recunosc, deoarece modelele de design sunt cu adevărat limbaj-agnostic. Ele sunt pur și simplu o colecție de cele mai bune strategii de design dezvoltate pentru a contracara problemele recurente cu care se confruntă în designul de software. Nimic mai mult, nimic mai puțin.
De exemplu, mai jos este o privire rapidă asupra modului în care am putea implementa o Singleton
în Javascript. Intenția rămâne aceeași: controlul creării obiectului și menținerea unui punct de acces global, dar implementarea diferă cu constructurile și semantica fiecărui limbaj.
var applicationCache = funcția () // Private stuff var instance; funcția initCache () return proxyUrl: "/bin/getCache.json", cachePurgeTime: 5000, permisiuni: read: "toată lumea", scrie: "admin"; // Returnarea publicului getInstance: function () if (! Instance) instance = initCache (); instanță retur; , purgeCache: funcție () instanță = null; ; ;
Pentru a cita un alt exemplu, jQuery folosește de asemenea modelul de proiectare a fațadelor, abstractizând complexitatea unui subsistem și expunând o interfață mai simplă utilizatorului.
Nu orice problemă necesită utilizarea unui model specific de design
Un cuvânt de precauție este necesar: nu excesiv! Nu orice problemă necesită utilizarea unui model specific de design. Trebuie să analizați cu atenție situația înainte de a vă stabili un model. Modelele de design de învățare ajută de asemenea la înțelegerea altor biblioteci, cum ar fi jQuery, Spring etc., care folosesc în mod greu multe astfel de modele.
Sper că, după ce ați citit acest articol, veți reuși să faceți un pas mai aproape de înțelegerea modelelor de design. Dacă aveți întrebări sau doriți să aflați un model de design suplimentar, anunțați-ne în comentariile de mai jos și voi face tot posibilul pentru a vă răspunde la întrebări!