HOME ABOUT US SERVICES OUR SCRIPTS OUR CLIENTS CONTACT US

 


Un mare interes in comunitatea Java apropos de lightweight container  care permite de asamblare componentele diferitelor proiecte intr-o aplicatie unica.La baza acestui container e un pattern  comun dupa cum e implementata legatura un concept sub numele generic de “Inversiune de Control”.In acest articol o sa aprofundez functionarea acestui pattern sub numele specific de “Dependenta de injectie” si incontrast cu alternative lui “Service Locator”. Alegerea intre acestea e mai putin importanta decat principal separarii configuratiilor de folosit.

Index

 

Unul dintre lucrurile de interes despre Java lumea enterprise este cantitatea mare de activitate în construirea de alternative la tehnologiile J2EE mainstream, o mare parte din acesta se întâmpla open source. Mare parte e o reactie datorita complexitatii mari in lumea officiala a J2EE mainstream, dar o mare este de a explora, de asemenea, alternative şi a veni cu idei creative. O problemă comună pentru a face faţă este legarea elementelor diferite: com conectezi impreuna aceasta arhitectura web de controller cu acea interfata a bazei de date cand sunt create de echipe diferite cu foarte putine cunostinte intre ele.O parte din frameworkuri au trecut peste ele iar altele incearca sa ofere o capabilitate generala de a asambla componentele din diferite straturi.Acestea sunt referiri la containele usoare(lightweight containers) ca PicoContainer si Spring
O serie de principii care stau la baza acestor containere sunt design interesant, lucruri care merg dincolo de aceste două containere specifice şi, platforma Java. Aici am dori să va ajutam in explorarea unor dintre aceste principii. Eu folosesc exemple în Java, dar ca cele mai multe dintre principiile mele sunt în mod egal aplicabile în medii OO, si în special. NET.


Componente şi servicii
Subiectul legarii elementelor împreună mă duce aproape imediat în probleme terminologia care înconjoară serviciul termenii şi componentele. Puteţi găsi articole lungi şi contradictorii cu privire la definirea acestor lucruri cu usurinta insa aici sunt utilizările mele actuale ale acestor termeni.
Termenul de component il folosesc pentru un soft care va fi folosit fara schimbari de aplicatie care e fara controlul celor care au scris-o Prin “Fara Schimbare” vreau sa zic ca aplicatia nu e capabila de a schimba codul sursa a componentei desi i-ar putea modifica comportamentul  prin extinderea in mai multe feluri permise de programatori.
Un serviciu este similar cu o componentă în măsura în care este folosit de către aplicaţii externe. Principala diferenţă este că mă aştept ca o componentă sa fie utilizata pe plan local (fişier jar, asamblare, DLL, sau o sursă de import). Un serviciu va fi folosit de la distanţă prin intermediul unor interfeţe de la distanţă, fie sincrone sau asincrone (serviciu web de exemplu, sistemul de mesagerie, RPC, sau socket.)
Eu folosesc mai ales de servicii în acest articol, dar o mare parte din aceeaşi logică poate fi aplicată la componentele locale. Într-adevăr, de multe ori ai nevoie de un fel de cadru componentă locală pentru a accesa cu uşurinţă un serviciu de la distanţă. Dar scriind  "componente sau servicii", este obositor de citit si scris iar, serviciile sunt mult mai la modă în acest moment.


Un Exemplu naiv
Pentru a face toate acestea mai concrete folosesc un exemplu. Ca toate exemplele mele este un super-simplu;suficient de mic pentru a fi ireal, dar sperăm, suficient pentru a vizualiza ceea ce se întâmplă fără a cădea în mlastina unui exemplu real.
În acest exemplu am scris o componentă care furnizeaza o lista de filme regizate de un anumit director. Această funcţie utilă e implementata printr-o metodă unică.

class MovieLister...

    public Movie[] moviesDirectedBy(String arg) {

        List allMovies = finder.findAll();

        for (Iterator it = allMovies.iterator(); it.hasNext();) {

            Movie movie = (Movie) it.next();

            if (!movie.getDirector().equals(arg)) it.remove();

        }

        return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);

    }

Implementarea acestei functii e  naiva intreaba un obiect Finder sa returneze  fiecare film de care stie. Apoi, cauta in aceasta lista sa returneze cele care sunt regizate de catre un director anume.Această piesă de naivitate nu am de gând să o repar, din moment ce este doar scheletul pentru punctul de real al acestui articol.
Punctul de real al acestui articol este acest obiect finder, sau mai ales cum ne conectam la obiectul Listener cu un anumit obiect Finder. Motivul pentru care acest lucru este interesant este că vreau meu ca minunata mea metodă moviesDirectedBy să fie complet independent de modul în care toate filmele sunt stocate. Deci, metoda se refera la un finder si tot ceea ce acel finder stie sa raspunda la metoda FindAll().Pot scoate acestea in evidenta daca definesc o interfata pentru finder:

public interface MovieFinder {
List findAll();
}


Acum, toate acestea sunt foarte bine decuplate, dar la un moment dat, trebuie sa vin cu o clasa concretă care sa imi listeze filmele.Deci pun codul pentru aceasta in constructorul clasei:

class MovieLister...
private MovieFinder finder;
public MovieLister() {
finder = new ColonDelimitedMovieFinder("movies1.txt");
}



Numele clasei implementate vine din faptul ca primesc lista dintr-un fisier cu coloane delimitate de tip text.Acum daca folosesc aceasta clasa
doar pentru mine totul e perfect.Dar ce se intampla cand prietenii mei isi doresc aceasta implementare si vor copia programul meu?Daca si ei vor
adauga numele filmelor intr-un fisier text delimitat totul e perfect.Daca ei au un alt nume pentru fisierul text cu filme e simplu de modificat
numele fisierului in proprietatile clasei.Dar daca ei au un mod total diferit de stocare a filmelor: intr-o baza de date SQL, intr-un fisier XML
sau in alt mod intr-un fisier text? In acest caz ne trebuie o clasa diferita care sa preia datele.Acum fiindca am definit o interfata MovieFinder
aceasta nu va altera metoda mea moviesDirectedBy .Dar tot trebuie sa gasesc o cale pentru a prelua o instanta a finderului .


Figure 1

Figura 1: dependenţe folosind o simpla creaţie în clasa Lister


Figura 1 arată dependenţele pentru această situaţie.Clasa MovieLister este dependentă de pe ambele interfete MovieFinder in momentul punerii în aplicare.
Am prefera, dacă ar depinde numai de interfata, dar atunci cum facem un exemplu de a lucra cu ea?

În cartea mea P a CEA , am descris această situaţie ca un Plugin . Clasa de conectare pentru finder nu e conectata in program in momentul compilarii deoarece nu stiu ce format au de gand prietenii mei sa foloseasca.
In alta ordine de idei dorim ca Lister ul meu sa lucreze cu orice tip de implementare iar aceasta implementare sa poata fi adaugata mai tarziu fara sa fie dependenta de mine.
Problema e cum as putea sa conectez aceasta in asa fel ca Listerul meu sa ignore clasa de implementare dar sa poata comunica cu o instanta si sa isi faca treaba?

Privind problema intr-un sistem real am putea avea zeci de astfel de servicii si componente.In fiecare caz putem folosi abstract aceste componente comunicand cu ele printr-o interfata (si folosind un adaptor in cazul in care componenta nu a fost proiectata cu o interfata in minte)
Dar daca dorim sa exportam acest sistem in moduri diferite , va trebui sa folosim pluginuri care sa ne ajute la interactiunea intre aceste servicii deci vom putea folosi implementari diferite in diferite situatii.
Deci problema de baza este cum vom asambla aceste pluginuri intr-o aplicatie? aceasta e una din problemele principale in cazul folosirii containerelor usoare si universal e rezolvata prin folosirea Inversiunii de control.


Inversare de Control
Cand aceste containere discuta despre modul cat sunt de utile ma simt destul de nedumerit. Inversiune de control este o caracteristică comună de cadre, astfel spunând că aceste containere speciale sunt uşoare, deoarece acestea utilizează inversiune de control este ca si cum as spune ca masina mea e speciala pentru ca are roti.
Întrebarea, este ce aspect de control inverseaza ele? Când am intrat prima oara in inversiunea de control a fost in interfata de utilizator. Mai nou interfeţe pentru utilizator sunt controlate de program sau de aplicaţie. Ai avea o secventa de comenzi ca " Introdu Numele", "introdu adresa" ; programul tau va afisa promptul si va culege raspunsurile cate unul. Cu grafică (sau chiar pe ecran)UIs the UI framework ar conţine această buclă principală şi programul dumneavoastră,va oferi evenimente pentru diferite campuri de pe ecran.
Pentru acest nou tip de containere inversiunea este despre modul în care acestea căuta un plugin de punere în aplicare. În exemplul meu naiv Lister de mai sus implementeaza finderul instantiindu-l direct.
Ca urmare cred ca avem nevoie de un nume specific pentru acest model. Inversiune de Control este un termen prea generic, şi, astfel, se consideră că este confuz. Ca un rezultat ne-am hotarat pe numele de injecţie de dependenţă .
Am de gând să încep prin a vorbi despre diferitele forme de dependenţă de injectare, dar va subliniez acum că nu e singurul mod de a elimina dependenţa din clasa aplicatiei catre pluginul implementarii. Celalalt model care poate fi utilizat e Service Locator, şi voi discuta despre acesta mai tarziu. Pentru moment am explicat termenul de Injectie dependenta.


Forme de injecţie de dependenţă
Ideea de bază a injectiei de dependenta este de a avea un obiect separat, un asamblor, care populează un câmp din clasa Lister cu o punere în aplicare corespunzătoare pentru interfata finder, rezultând într-o diagramă dependenţă de-a lungul liniilor din figura 2
Figure 2
Figura 2: dependenţe pentru un injector de dependenţă


Există trei stiluri principale de injecţie de dependenţă. Acestea sunt Constructor injecţie, Setter injecţie, şi interfaţă de injecţie. Dacă aţi citit despre lucrurile astea în discuţiile actuale despre Inversiune de Control veţi auzi aceste denumiri în continuare de tip 1 CIO (injecţie interfaţă), de tip 2 CIO (injecţie setter) şi tipul 3 CIO (injecţie constructorului). Numele numeric mi se pare mai greu de folosit de aceea am folosit denumirile aici.
Injectarea Constructorului cu PicoContainer
Voi începe cu modul în care arată acest lucru Injectarea se face folosind un recipient usor numit PicoContainer . Am început cu acesta in primul rând pentru că mai multi dintre colegii mei de la ThoughtWorks sunt foarte activi în dezvoltarea cu PicoContainer (da, e un fel de nepotism corporativ.)
PicoContainer utilizează un constructor pentru a decide cum să injectaţi o implementare Finder în clasa Lister. Pentru ca aceasta să funcţioneze, clasa movie lister trebuie să declare un constructor care include tot ceea ce are nevoie injectat.

class MovieLister...
public MovieLister(MovieFinder finder) {
this.finder = finder;
}


Finderva fi gestionat de containerul Pico, şi ca atare va avea numele fisierului text injectat de container.

class ColonMovieFinder...
public ColonMovieFinder(String filename) {
this.filename = filename;
}


containerului Pico apoi trebuie sa i se comunice care clasa trebuie sa asocieze cu fiecare interfata si ce obiect string trebuie sa injecteze in Finder.

 

private MutablePicoContainer configureContainer() {
MutablePicoContainer pico = new DefaultPicoContainer();
Parameter[] finderParams = {new ConstantParameter("movies1.txt")};
pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
pico.registerComponentImplementation(MovieLister.class);
return pico;
}


Acest cod de configurare este setat de obicei într-o clasă diferită. Pentru exemplul nostru, fiecare prieten care utilizează Lister al meu ar putea scrie codul de configurare corespunzător in clasa lor de instalare. Desigur e un lucru comun sa pastrezi aceste fisiere de configurare in fisiere config separate. Puteţi scrie o clasa pentru a citi un fişier de configurare şi aceea va instantia recipientul corespunzător. Deşi PicoContainer nu conţine această funcţionalitate în sine, există un proiect de strâns legat numit NanoContainer care oferă învelişurile corespunzătoare pentru a vă permite să aveţi fişiere de configurare XML. Un astfel de container nano va analiza XML-ul şi apoi va configura un recipient Pico container. Filosofia proiectului este de a separa fişierul de configurare format din mecanismul de bază.
Pentru a utiliza recipientul putem scrie un cod de genul.

public void testWithPico() {
MutablePicoContainer pico = configureContainer();
MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}


Deşi în acest exemplu am folosit constructor de injecţie , PicoContainer sprijină, de asemenea injecţia setter, deşi dezvoltatorii prefera sa injecteze constructorul.
Injectarea Setter cu Spring
Spring framework este folosit in general pentru dezvoltare JAVA Enterprise. Acesta include straturi de abstractizare pentru tranzacţii, persistence frameworks, dezvoltare aplicatii web si JDBC. Ca si PicoContainer suportă atât constructor cat si injectare setter, dar dezvoltatorii săi tind să prefere injecţie setter - ceea ce îl face o alegere potrivită pentru acest exemplu.
Pentru a obţine movie lister a accepta injecţie am definit o metodă de setare pentru acest serviciu

class MovieLister...
private MovieFinder finder;
public void setFinder(MovieFinder finder) {
this.finder = finder;
}


În mod similar definesc un setter pentru numele fişierului.

class ColonMovieFinder...
public void setFilename(String filename) {
this.filename = filename;
}


Al treilea pas este de a seta configurarea pentru fişiere. Spring sprijină configurare via fisiere XML sau via cod , dar XML este modul de aşteptat.
<beans>
<bean id="MovieLister" class="spring.MovieLister">
<property name="finder">
<ref local="MovieFinder"/>
</property>
</bean>
<bean id="MovieFinder" class="spring.ColonMovieFinder">
<property name="filename">
<value>movies1.txt</value>
</property>
</bean>
</beans>


Testul apoi arata ca .

public void testWithSpring() throws Exception {
ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}

Interfaţa de injecţie

a treia tehnica de injectie e sa definesti si sa folosesti o interfata pentru injectie. Avalon este un exemplu de framework care foloseşte această tehnică local.
Voi vorbi un pic mai mult despre asta mai târziu, dar în acest caz, am de gând să-l utilizaţi cu unele exemple de cod simple.

Cu aceasta tehnica Încep prin a defini o interfaţă pe care voi folosi pentru a efectua o injectare.
Iată interfata pentru injectarea un Movie finder într-un obiect.


public interface InjectFinder {
void injectFinder(MovieFinder finder);
}


Această interfaţă ar putea fi definită de către oricine oferă interfaţa MovieFinder. Aceasta trebuie să fie puse în aplicare de orice clasa care vrea să utilizeze un Finder, cum ar fi Lister.
class MovieLister implements InjectFinder...
public void injectFinder(MovieFinder finder) {
this.finder = finder;
}


Folosesc o abordare similară pentru a injecta numele fişierului în implementarea Finder.


public interface InjectFinderFilename {
void injectFilename (String filename);
}
class ColonMovieFinder implements MovieFinder, InjectFinderFilename......
public void injectFilename(String filename) {
this.filename = filename;
}



Apoi, ca de obicei, am nevoie de un cod de configurare pentru a lega implementarea. Pentru simplitate o voi face in cod.


class Tester...
private Container container;
private void configureContainer() {
container = new Container();
registerComponents();
registerInjectors();
container.start();
}

Această configuraţie a două etape ce inregistreaza componentele prin cheile de căutare este destul de similara cu alte exemple.


class Tester...
private void registerComponents() {
container.registerComponent("MovieLister", MovieLister.class);
container.registerComponent("MovieFinder", ColonMovieFinder.class);
}

Un nou pas este de a înregistra injectoarele care vor injecta componentele dependente. Fiecare interfata de injectie are nevoie de un cod pentru a injecta obiectele dependente. Aici obtin acest lucru prin înregistrarea obiectelor injectore cu containerul.
Fiecare obiect injector implementeaza interfata injector.



class Tester...
private void registerInjectors() {
container.registerInjector(InjectFinder.class, container.lookup("MovieFinder"));
container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector());
}

public interface Injector {
public void inject(Object target);
}


În cazul în care este dependentă de o clasă scrisa pentru acest recipient, este logic pentru componenta să implementeze interfata injector în sine, aşa cum am face aici cu movie Finder .
Pentru clasele generice, cum ar fi string, am folosit o clasă interioară în codul de configurare.

class ColonMovieFinder implements Injector......
public void inject(Object target) {
((InjectFinder) target).injectFinder(this);
}

class Tester...
public static class FinderFilenameInjector implements Injector {
public void inject(Object target) {
((InjectFinderFilename)target).injectFilename("movies1.txt");
}
}



Testele si folosirea containerului.

class IfaceTester...
public void testIface() {
configureContainer();
MovieLister lister = (MovieLister)container.lookup("MovieLister");
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}

Containerul foloseste interfata de injecţie declarata ca sa inteleaga dependentele iar injectoarele pentru a injecta dependenta corectă.


Folosind un Locator Service
Principalul beneficiu al unui injector de Dependenţa este că elimină dependenţa pe care clasa MovieLister o are in implementarea concreta MovieFinder . Acest lucru imi permite sa dau acest lister unui prieten pentru a il implementa in mediul lui. Injecţia nu este singura modalitate de a sparge această dependenţă, alta este să utilizaţi un serviciu de localizare .
Ideea de bază în spatele unui serviciu de localizare este de a avea un obiect care ştie cum să facă rost de toate serviciile care o cerere ar putea avea nevoie. Deci, un serviciu de localizare pentru această aplicare ar fi o metodă care returnează un movie finder atunci când este nevoie. Desigur, acest lucru doar schimba sarcina un pic, mai avem pentru a obţine locator în Lister, care rezultă în dependenţele din figura 3
Figure 3
Figura 3: dependenţe pentru un Locator Service
În acest caz, voi folosi ServiceLocator ca un singleton Registry . Lister-ul poate folosi aceasta pentru a obţine Finder atunci când se instanţiată.

class MovieLister...
MovieFinder finder = ServiceLocator.movieFinder();

class ServiceLocator...
public static MovieFinder movieFinder() {
return soleInstance.movieFinder;
} private static ServiceLocator soleInstance;
private MovieFinder movieFinder;


Ca şi în abordarea de injectare, trebuie să configuraţi serviciul de localizare. Aici o fac în cod, dar nu este greu de a utiliza un mecanism care ar putea citi datele corespunzătoare dintr-un fişier de configurare.

class Tester...
private void configure() {
ServiceLocator.load(new ServiceLocator(new ColonMovieFinder("movies1.txt")));
}

class ServiceLocator...
public static void load(ServiceLocator arg) {
soleInstance = arg;
}
public ServiceLocator(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}


Aici este codul de testare.


class Tester...
public void testSimple() {
configure();
MovieLister lister = new MovieLister();
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}

Am auzit de multe ori motivul că aceste tipuri de servicii de localizare sunt un lucru rău, deoarece acestea nu sunt testabile si nu se pot substitui implementări pentru ei. Desigur, aveţi posibilitatea să le creati prost si aceasta genereaza acest tip de probleme. În acest caz, instanta serviciului locator este doar un suport de date simplu. Se pot crea cu uşurinţă locatori cu implementările serviciilor mele de test.

Un mod de a gândi în acest sens este faptul că serviciul Locator este un registru nu este un Singleton. Un Singleton oferă o modalitate simplă de punere în aplicare a unui registru, dar în punerea in aplicare această decizie este uşor de schimbat.

Folosind o interfata segregata pentru Locator

Una dintre problemele cu abordarea simpla de mai sus, este că MovieLister depinde complet de clasa de serviciul locator , chiar dacă foloseşte un singur serviciu.
Putem reduce aceasta folosind o interfata segregata. În acest fel, în loc de a utiliza pe deplin interfaţa serviciului locator , Lister poate declara doar o portiune din interfata de care are nevoie.

În această situaţie, furnizorul Lister-ului ar oferi, de asemenea, o interfata de localizare de care are nevoie pentru a obtine Finder-ul.


public interface MovieFinderLocator {
public MovieFinder movieFinder();


Locatorului, apoi trebuie să implementeze această interfaţă pentru a oferi acces la Finder.

MovieFinderLocator locator = ServiceLocator.locator();
MovieFinder finder = locator.movieFinder();

public static ServiceLocator locator() {
return soleInstance;
} public MovieFinder movieFinder() {
return movieFinder;
} private static ServiceLocator soleInstance;
private MovieFinder movieFinder;



Veţi observa că, deoarece vrem sa folosim o interfaţă, nu mai putem accesa serviciile prin metode statice .
Trebuie sa folosim o clasa care sa obtina instanta locatorului si apoi sa folosim aceasta pentru a obtine ce avem nevoie.

Un Dynamic Service Locator

Exemplul de mai sus a fost static, în care clasa serviciului locator dispune de metode pentru fiecare dintre serviciile
de care aveţi nevoie.
Aceasta nu este singura modalitate de a face aceasta, puteţi face, de asemenea, un localizator serviciu dinamic care vă
permite să introduceti orice serviciu de care aveţi nevoie în ea şi să aleaga in momentul rularii.

În acest caz, locatorul de servicii utilizează o hartă în loc de campuri pentru fiecare dintre servicii, şi oferă metode
generice pentru a obţine şi incarca serviciile.

class ServiceLocator...
private static ServiceLocator soleInstance;
public static void load(ServiceLocator arg) {
soleInstance = arg;
} private Map services = new HashMap();
public static Object getService(String key){
return soleInstance.services.get(key);
} public void loadService (String key, Object service) {
services.put(key, service);
}

Configurarea implică incarcarea serviciilor cu o cheie apropiata..


class Tester...
private void configure() {
ServiceLocator locator = new ServiceLocator();
locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));
ServiceLocator.load(locator);
}

Eu folosesc serviciul prin utilizarea aceluiasi şir de chei.

class MovieLister...
MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");


Pe ansamblu, îmi displace această abordare. Deşi este cu siguranţă flexibila, nu este foarte explicita.
Singurul mod în care pot afla cum să ajung la un serviciu este prin cheile textuale. Prefer metode explicite,
deoarece este mai uşor să le găseşti uitandu-te la definitia interfetei.

Folosind atât o localizare şi injectare cu Avalon

Dependenţa de injectare şi un localizator de servicii nu sunt neaparat concepte ce se exclud reciproc.
Un bun exemplu de utilizare a ambelor împreună este cadrul Avalon. Avalon foloseşte un serviciu de localizare,
dar utilizează componente de injectare pentru ai spune unde să găsească localizarea.

Berin Loritsch mi-a trimis această versiune simplă a exemplului meu utilizând Avalon.


public class MyMovieLister implements MovieLister, Serviceable {
private MovieFinder finder;
public void service( ServiceManager manager ) throws ServiceException {
finder = (MovieFinder)manager.lookup("finder");
}



aceasta metoda este un exemplu de injecţie de interfaţă, permiţând unui container de a injecta un manager
de servicii în MyMovieLister.
Managerul de serviciu este un exemplu de Locator de serviciu. În acest exemplu, Lister nu stochează managerul
într-un câmp, ci il foloseste imediat pentru a gasi finder-ul .


Decide ce optiune sa utilizezi
Până acum m-am concentrat pe explicarea cum vad eu aceste modele şi variaţiile acestora. Acum pot începe să vorbesc despre argumentele lor pro si contra pentru a intelege pe care să le folosim şi când.
Service Locator vs Dependenţa de injecţie
Alegerea fundamentală între Service Locator şi injecţie de dependenţă. Primul punct este că ambele implementari furnizează o decuplarea fundamentala care lipseşte în exemplul naiv - în ambele cazuri, codul aplicatiei este independent de aplicarea concretă a interfeţei de servicii. Diferenţa importantă între cele două modele este modul în care punerea în aplicare este prevăzută pentru clasa cererii. Cu serviciu de localizare clasa cerere solicită în mod explicit locatorului printr-un mesaj. Cu injecţie nu există o cerere explicită, serviciul apare în clasa cerere - prin urmare, inversiunea de control.
Inversiune de control este o trăsătură comună de cadre, dar vine cu un preţ. Acesta tinde să fie greu de înţeles şi duce la probleme atunci când trebuie depanata.
Diferenţa esenţială este că, cu un Locator Service fiecare utilizator al unui serviciu are o dependenţă la locator. Locatorul poate ascunde dependenţe catre alte implementări, dar trebuie sa vedeti locatorul. Deci, decizia între locator şi injector depinde dacă dependenţa este sau nu o problemă.
Utilizarea injecţiei de dependenţă poate favoriza vedera de ansamblu a componentelor de dependenta. Cu injector dependenţă poti sa vezi mecanismul de injectare, cum ar fi constructor, şi dependenţele. Cu serviciul de localizare va trebui să căutaţi în codul sursă pentru apelurile la locator.
O mulţime de acest lucruri depind de natura de utilizator al serviciului. Dacă construiesti o aplicatie cu diverse clase care folosesc un serviciu atunci dependenta claselor aplicatiei catre locator nu e mare lucru. În exemplul meu de a oferi un Lister Movie prietenilor mei, folosind apoi un localizator ca serviciu funcţionează destul de bine. În acest tip de scenariu nu văd injectorul de inversiune deloc convingătoare.
Diferenţa vine în cazul în care Lister este o componentă pe care o ofer altor persoane care scriu o aplicatie. În acest caz, eu nu stiu prea multe despre API-urile de locatoare serviciu pe care clientii mei au de gând să utilizeze. Fiecare client ar putea avea propriile lor locatoarele incompatibile cu serviciul. Fiecare client poate scrie un adaptor care se potriveşte cu interfata mea de localizare pentru cazul lor , dar în orice caz, eu am nevoie de a vedea primul locator pentru a cauta interfata mea specifica. Şi odată ce adaptorul apare atunci simplitatea de conexiune directă la o localizare este la început să alunece.
Deoarece cu un injector nu aveţi o dependenţă de la o componentă la injectorul, componenta nu poate obţine servicii suplimentare de la injectoru odată ce a fost configurat.
Un motiv comun pentru a prefera injecţia de dependenţă este faptul că e mai uşor de testat. Punctul de aici este că pentru a face teste, trebuie să înlocuiţi cu uşurinţă implementarea serviciului real . Cu toate acestea nu există într-adevăr nici o diferenţă aici injecţie de dependenţă şi locatorul de servicii: ambele sunt foarte pretabile la stubbing. Bănuiesc această observaţie vine din proiecte unde programatorii nu fac efortul de a asigura că serviciul lor de localizare poate fi uşor înlocuit.
Desigur, problema testarii e exacerbata de medii de componente care sunt foarte intruzive, cum ar fi cadrul de Java EJB. Părerea mea este că aceste tipuri de cadre ar trebui să reducă impactul acestora asupra codului aplicatiei, şi în special nu ar trebui să facă lucruri care incetinesc editarea-executarea ciclului. Utilizarea de plugin-uri pentru a înlocui componentele grele are o mulţime de avantaje pentru acest proces, care este vital pentru practici cum ar fi Test Driven Development.

Constructor faţă de Injectarea Setter
Pentru asocierea serviciului, întotdeauna trebuie să ai unele convenţii în scopul de a lega lucruri împreună. Avantajul de injectare este în primul rând că are nevoie de convenţiile foarte simple cel puţin pentru constructor şi setter. Nu trebuie să faceţi nimic ciudat in componenta dvs. şi este destul de simplu pentru un injector pentru a obţine totul configurat.
Interfaţa injectiei este mai invaziva, deoarece va trebui să scrie o mulţime de interfeţe pentru a obţine toate lucrurile sortate. Pentru un mic set de interfeţe cerute de container, cum ar fi în abordarea lui Avalon , acest lucru nu este prea rău. Dar este mult de munca pentru asamblarea componentelor şi dependenţelor.
Posibilitatea de a alege între setter şi injecţie constructor este interesanta deoarece aceasta reflectă o problemă mai generală cu programarea orientata pe obiecte - ar trebui să vă completaţi câmpurile dintr-un constructor sau cu setteri.

Un alt avantaj cu initializare constructor este că vă permite să se ascundeti în mod clar toate câmpurile care sunt imuabile neoferind setterul. Cred că acest lucru este important - daca ceva nu ar trebui să se modifice atunci lipsa unui setter comunică acest lucru foarte bine. Dacă utilizaţi setteri pentru iniţializare, atunci acest lucru poate deveni o durere. (Într-adevăr, în aceste situaţii, am prefera sa evite Convenţia de la setarea de obicei, aş prefera o metoda ca initFoo , să subliniez că este ceva ce ar trebui să facă doar la naştere.)
Dar, in orice situaţie există şi excepţii. Dacă aveţi o mulţime de parametri constructor lucrurile pot deveni dezordonate, în special în limbi, fără parametri de cuvinte cheie. Este adevărat că un constructor lung este adesea un semn de obiect supra-aglomerat, care ar trebui să fie împărţit, dar există cazuri în care este ceea ce aveţi nevoie.
Dacă aveţi mai multe moduri de a construi un obiect valabil, poate fi greu pentru a demonstra acest lucru prin constructori, deoarece constructorii pot varia numai cu privire la numărul şi tipul de parametri. Acest lucru este atunci când metodele Factory intra în joc, acestea pot utiliza o combinaţie a constructorilor private şi să pună în aplicare setterii . Problema cu Factory Methods pentru asamblare este că ele sunt de obicei văzute ca metode statice, şi nu puteţi avea cele de pe interfeţe. Puteţi face o fabrica de clasă, dar care devine doar un alt exemplu de serviciu. Un serviciu de fabrica este adesea o tactică bună, dar încă mai trebuie să instantia fabrica folosind una dintre tehnicile de aici.
Constructorii, de asemenea, pot suferi, dacă aveţi parametri simpli, cum ar fi siruri de caractere. Cu injecţia setter puteţi da fiecarui setter un nume pentru a indica ce şir trebuie să facă.
Dacă aveţi constructori multipli şi moştenire, atunci lucrurile pot deveni deosebit de ciudate. Aceasta poate duce la o explozie si mai mare de constructori.
În ciuda dezavantajelor preferinţa mea este de a începe cu injecţie de constructor, dar să fie gata pentru a comuta la setter injectare de îndată ce problemele subliniate mai sus încep să devină o problemă.
Această problemă a condus la o multime de dezbateri între diversele echipe care furnizează injectoarele de dependenţă, ca parte din cadrele lor. Cu toate acestea se pare că cei mai mulţi oameni care construiesc aceste cadre si-au dat seama că este important să se sprijine ambele mecanisme, chiar dacă există o preferinţă pentru unul dintre ei.
Cod sau fişiere de configurare
Un subiect separat, dar de multe ori discutat este dacă să utilizaţi fişierele de configurare sau de cod de pe un API pentru a legarea a serviciilor. Pentru majoritatea aplicaţiilor care sunt susceptibile de a fi dislocate în mai multe locuri, un fişier de configurare separat de obicei are cel mai mult sens. Aproape tot timpul acest lucru va fi un fişier XML, şi acest lucru are sens. Cu toate acestea, există cazuri în care este mai uşor să foloseşti codul program pentru a face adunarea. Un caz este cand aveţi o aplicaţie simpla, care nu are o mare variaţie de desfăşurare. În acest caz, un pic de cod poate fi mai clar decât un fişier XML separat.
Un caz contrastant este în cazul în care ansamblul este destul de complex, si implică paşi condiţionati. Odată ce începeti să stapaniti de aproape limbajul de programare XML, care poate face probleme uneori e mai bine să utilizaţi un limbaj real care are toate sintaxa pentru a scrie un program clar. Puteţi apoi scrie o clasă constructor care face de asamblare. Dacă aveţi scenarii distincte constructorii vă pot oferi mai multe clase constructor .
Eu de multe ori cred că programatorii sunt supra-dornici de a defini fişiere de configurare. Adesea, un limbaj de programare face un mecanism de configurare simplă şi puternică. limbi moderne pot compila uşor asamblori mici, care pot fi folosite pentru a asambla plugin-uri pentru sisteme mai mari. Cazul în care elaborarea este dificila, atunci există limbaje care pot lucra, de asemenea, bine.
Se spune adesea că fişierele de configurare nu ar trebui să utilizeze un limbaj de programare, deoarece ele trebuie să fie editat de către non-programatori. Dar cât de des este acest caz? Oamenii într-adevăr aştepta non-programatori pentru a modifica nivelurile de izolare tranzacţiei de o aplicatie complexa server-side? fişiere non-lingvistice de configurare funcţionează bine doar în măsura în care sunt simple. În cazul în care acestea devin complexe, atunci este timpul să se gândească la utilizarea unui limbaj de programare adecvată.
Un lucru il vedem în lumea Java în acest moment este o cacofonie de fisiere de configurare, în cazul în care fiecare componentă are propriile sale fisiere de configurare, care sunt diferite de orice alt limbaj. Dacă utilizaţi o duzină de aceste componente, puteţi ajunge foarte uşor cu o duzină de fişiere de configurare care trebuiesc pastrate în sincronizare.
Sfatul meu aici este de a oferi întotdeauna o modalitate de a face toate configuraţiile uşor cu o interfaţă programatic, şi apoi a trata un fişier de configurare separat ca o caracteristică opţională. Puteţi construi cu uşurinţă fişierul de configurare de manipulare pentru a utiliza interfaţe programatice.
Separarea de configurare de la utilizare
Problema importanta în toate acestea este de a asigura că această configuraţie de servicii este separata de utilizarea lor. Într-adevăr, acesta este un principiu fundamental de design care exista cu separarea de interfeţe de punere în aplicare. Este ceva ce vedem în interiorul unui program orientat pe obiect când logica condiţionată decide care clasa trebuie instantiata, şi apoi evaluările viitoare de care sunt condiţionate se fac prin polimorfism, mai degrabă decât prin codul condiţionată duplicat.
În cazul în care această separare este utilă în cadrul unui singur cod de bază, este deosebit de important atunci când utilizaţi elemente străine, cum ar fi componente şi servicii. Prima întrebare este dacă doriţi să amâne alegerea de punere în aplicare a clasei de implementare speciala. Dacă, deci va trebui să utilizaţi punerea în aplicare de plug-in. Odată ce incepeti sa folositi plugin-uri, atunci este esential ca asamblarea plugin-uri sa se faca separat de restul cererii, astfel încât să puteţi înlocui cu uşurinţă diferite configuraţii pentru implementări diferite. Cum se realiza acest lucru este secundar. Acest mecanism de configurare poate configura fie un localizator de serviciu, sau de a folosi injecţie pentru a configura obiectele direct.


Unele probleme în continuare
În acest articol, m-am concentrat pe problemele de bază de configurare serviciu folosind Dependency Injection şi Service Locator. Există unele subiecte in acest context acest care merita, de asemenea, o atenţie, dar nu am avut timp încă să le discut. În special, există problema de comportament al ciclului de viaţă. Unele componente au evenimente distincte ciclului de viaţă.Un alt aspect este interesul crescut în utilizarea de idei aspect orientat cu aceste containere. Deşi nu am considerat că acest material în articol în acest moment, sper să scriu mai multe despre acest lucru fie prin extinderea acestui articol sau prin scrierea altuia..
Puteţi afla mai multe despre aceste idei, uitandu-va la site-uri web dedicate containere uşoare. Navigarea din Pico Container şi Spring site-uri web va duce la tine în mai multe discuţii de aceste probleme şi un început pe unele dintre problemele în continuare.


Gânduri de încheiere
Graba actuala de containere uşoare au toate un model comun care stau la modul în care acestea fac asamblarea de serviciu - injectorul model de dependenţă. Dependenţa Injection este o alternativă utilă la Service Locator. Când creem clase de aplicare cele două sunt aproximativ echivalente, dar cred că Service Locator are o margine uşoară din cauza comportamentului său mai simplu. Toate acestea, dacă preferati constructii de clase pentru a fi utilizati în aplicaţii multiple, atunci Dependenţa Injection este o alegere mai bună.
Dacă utilizaţi Dependency Injection există un număr de stiluri de a alege între. Aş dori să vă sugerez sa urmariti injecţia constructorului cu excepţia cazului în care ruleaza în una din problemele specifice cu această abordare, în acest caz mai bine comutati la injectare setter .
Posibilitatea de a alege între Service Locator şi Dependenţa de injecţie este mai puţin importantă decât principiul separării configurare serviciu din utilizarea de servicii în cadrul unei cereri.



Mulţumiri
Sincere mulţumiri pentru persoanele care m-au ajutat cu acest articol. Rod Johnson, Paul Hammant, Joe Walnes, Aslak Hellesøy, Jon Tirsén şi Bill Caputo m-au ajutat a lua la trântă cu aceste concepte şi a comenta cu privire la proiectele timpurii a acestui articol. Berin Loritsch şi Hamilton Verissimo de Oliveira a furnizat unele sfaturi foarte utile despre cum se potriveşte Avalon inch W Dave Smith a persistat în întrebări despre codul meu de interfaţă de configurare iniţială de injectare şi, astfel, ma facut sa se confrunte cu faptul că aceasta a fost o prostie. Gerry Lowry mi-a trimis o mulţime de remedieri tipo - suficient pentru a trece pragul mulţumiri.
Revizuirile Semnificative
23 ianuarie 2004: refacut codul configuraţia exemplu injecţie interfaţă.
16 ianuarie 2004: Adaugat un scurt exemplu de cât de localizare şi de injectare cu Avalon.
14 ianuarie 2004: prima publicare

 

HOME :: ABOUT :: SERVICES :: SCRIPTS :: CLIENTS :: CONTACT

© SoftAcid All rights Reserved