CATEGORII DOCUMENTE |
Scheme deare
Experienta acumulata in realizarea unor aplicatii cu clase a condus la recunoasterea si inventarierea unor scheme (sabloane) de proiectare ("Design Patterns"), adica a unor grupuri de clase si obiecte care coopereaza pentru realizarea unor functii. In cadrul acestor scheme exista clase care au un anumit rol in raport cu alte clase si care au primit un nume ce descrie acest rol; astfel exista clase iterator, clase observator, clase "fabrica" de obiecte, clase adaptor s.a. Dincolo de detaliile de implementare se pot identifica clase cu aceleasi responsabilitati in diferite aplicatii.
Trebuie remarcat ca si clasele predefinite JDK au evoluat de la o versiune la alta in sensul extinderii functionalitatii si aplicarii unor scheme de proiectare reutilizabile. Clasele JDK (in special clase JFC) care urmeaza anumite scheme de proiectare au primit nume care sugereaza rolul clasei intr-un grup de clase ce interactioneaza : clase iterator, clase adaptor, clase "model" s.a.
Definitii posibile pentru schemele de proiectare folosite in aplicatii cu clase:
- Solutii optime pentru probleme comune de proiectare.
- Abstractii la un nivel superior claselor, obiectelor sau componentelor.
- Scheme de comunicare (de interactiune) intre obiecte.
Argumentul principal in favoarea studierii si aplicarii schemelor de clase este acela ca aplicarea acestor scheme conduce la programe mai usor de modificat. Aceste obiective pot fi atinse in general prin clase (obiecte) "slab" cuplate, care stiu cat mai putin unele despre altele.
Principalele recomandari care rezulta din analiza schemelor de proiectare si aplicatiilor sunt:
- Compozitia (agregarea) este preferabila in raport cu derivarea.
- Proiectarea cu interfete si clase abstracte este preferata fata de proiectarea cu clase concrete, pentru ca permite separarea utilizarii de implementare.
- Este recomandata crearea de clase si obiecte suplimentare, cu rol de intermediari, pentru decuplarea unor clase cu roluri diferite.
O clasificare uzuala a schemelor de proiectare distinge trei categorii:
- Scheme "creationale" (Creational patterns) prin care se genereaza obiectele necesare.
- Scheme structurale (Structural patterns), care grupeaza mai multe obiecte in structuri mai mari.
- Scheme de interactiune (Behavioral patterns), care definesc comunicarea intre clase.
Cea mai folosita schema de creare obiecte in Java este metoda fabrica, prezenta in mai multe pachete, pentru crearea de diverse obiecte. Se mai foloseste schema pentru crearea de clase cu obiecte unice ('Singleton') in familia claselor colectie.
Schemele structurale folosite in JDK sunt clasele "decorator" din pachetul "java.io" si clasele "adaptor" din "javax.swing".
Schemele de interactiune prezente in JDK sunt clasele iterator ("java.util") si clasele observator si observat ("java.util") extinse in JFC la schema cu trei clase MVC ( Model-View-Controller).
Clase si metode "fabrica" de obiecte
In cursul executiei un program creeaza noi obiecte, care furnizeaza aplicatiei datele si metodele necesare. Majoritatea obiectelor sunt create folosind operatorul new, pe baza ipotezei ca exista o singura clasa, care nu se mai modifica, pentru aceste obiecte.
O solutie mai flexibila pentru crearea de obiecte este utilizarea unei fabrici de obiecte, care lasa mai multa libertate in detaliile de implementare a unor obiecte cu comportare predeterminata.
O fabrica de obiecte ("Object Factory") permite crearea de obiecte de tipuri diferite, dar toate subtipuri ale unui tip comun (interfata sau clasa abstracta).
O fabrica de obiecte se poate realiza in doua forme:
- Ca metoda fabrica (de obicei metoda statica) dintr-o clasa, care poate fi clasa abstracta ce defineste tipul comun al obiectelor fabricate. Aceasta solutie se practica atunci cand obiectele fabricate nu difera mult intre ele.
- Ca o clasa fabrica, atunci cand exista diferente mai mari intre obiectele fabricate.
Alegerea (sub)tipului de obiecte fabricate se poate face fie prin parametri transmisi metodei fabrica sau constructorului de obiecte "fabrica", fie prin fisiere de proprietati (fisiere de configurare).
Metode si clase fabrica pot fi intalnite in diverse pachete din JSE (Java Standard Edition ) si, mai ales, in JEE (Java Enterprise Edition).
Putem deosebi doua situatii in care este necesara utilizarea unei metode "fabrica" in locul operatorului new:
- Pentru a evita crearea de obiecte identice (economie de memorie si de timp); metoda fabrica va furniza la fiecare apel o referinta la un obiect unic si nu va instantia clasa. Rezultatul metodei este de un tip clasa instantiabila.
- Pentru ca tipul obiectelor ce trebuie create nu este cunoscut exact de programator, dar poate fi dedus din alte informatii furnizate de utilizator, la executie. Rezultatul metodei este de un tip interfata sau clasa abstracta, care include toate subtipurile de obiecte fabricate de metoda (fabrica 'polimorfica').
Utilizarea fabricilor de obiecte poate fi privita ca un pas inainte pe calea separatiei interfata-implementare, in sensul ca permite oricate implementari pentru o interfata, cu orice nume de clase si cu orice date initiale necesare fabricarii obiectelor.
O metoda fabrica poate fi o metoda statica sau nestatica. Metoda fabrica poate fi apelata direct de programatori sau poate fi apelata dintr-un constructor, ascunzand utilizatorilor efectul real al cererii pentru un nou obiect.
O metoda fabrica de un tip clasa instantiabila tine evidenta obiectelor fabricate si, in loc sa produca la fiecare apel un nou obiect, produce referinte la obiecte deja existente. Un exemplu este metoda "createEtchedBorder" din clasa BorderFactory care creeaza referinte la un obiect chenar unic, desi se pot crea si obiecte chenar diferite cu new :
JButton b1 = new JButton ("Etched1"), b2= new JButton("Etched2");
// chenar construit cu "new"
b1.setBorder (new EtchedBorder ()); // chenar "gravat"
// chenar construit cu metoda fabrica
Border eb = BorderFactory.createEtchedBorder();
b2.setBorder (eb);
Pentru acest fel de metode fabrica exista cateva variante:
- Metoda are ca rezultat o referinta la un obiect creat la incarcarea clasei:
public class BorderFactory // neinstantiabila
static final sharedEtchedBorder = new EtchedBorder();
public static Border createEtchedBorder()
} // alte metode fabrica in clasa
Clasa BorderFactory (pachetul "javax.swing") reuneste mai mai multe metode "fabrica" ce produc obiecte chenar de diferite forme pentru componentele vizuale Swing. Toate obiectele chenar apartin unor clase care respecta interfata comuna Border si care sunt fie clase predefinite, fie clase definite de utilizatori. Exemple de clase chenar predefinite: EmptyBorder, LineBorder, BevelBorder, TitleBorder si CompundBorder (chenar compus).
- Metoda creeaza un obiect numai la primul apel, dupa care nu mai instantiaza clasa.
Un exemplu de metoda fabrica controlabila prin argumente este metoda "getInstance" din clasa Calendar, care poate crea obiecte de diferite subtipuri ale tipului Calendar , unele necunoscute la scrierea metodei dar adaugate ulterior.
Obiectele de tip data calendaristica
au fost create initial in Java ca instante ale clasei Date, dar ulterior s-a optat pentru o solutie mai generala, care sa
tina seama de diferitele tipuri de calendare folosite pe glob. Clasa abstracta Calendar (din "java.util") contine
cateva metode statice cu numele "getInstance" (cu si fara parametri), care
fabrica obiecte de tip Calendar. Una
din clasele instantiabile derivata din Calendar
este GregorianCalendar, pentru tipul
de calendar folosit in Europa si in
public static Calendar getInstance()
Obiectele de tip Calendar se pot utiliza direct sau transformate in obiecte Date:
Calendar azi = Calendar.getInstance();
Date now = azi.getTime(); // conversie din Calendar in Date
System.out.println (now);
Metoda "getInstance" poate avea unul sau doi parametri, unul de tip TimeZone si altul de tip Local, pentru adaptarea orei curente la fusul orar (TimeZone) si a calendarului la pozitia geografica (Local). Tipul obiectelor fabricate este determinat de acesti parametri.
Alte metode fabrica care produc obiecte adaptate particularitatilor locale (de tara) se afla in clase din pachetul "java.text": NumberFormat, DateFormat, s.a. Adaptarea se face printr-un parametru al metodei fabrica care precizeaza tara si este o constanta simbolica din clasa Locale.
Afisarea unei date calendaristice se poate face in mai multe forme, care depind de uzantele locale si de stilul dorit de utilizator (cu sau fara numele zilei din saptamana, cu nume complet sau prescurtat pentru luna etc.). Clasa DateFormat contine metoda "getDateInstance" care poate avea 0,1 sau 2 parametri si care poate produce diferite obiecte de tip DateFormat. Exemple:
Date date = new Date();
DateFormat df1 = DateFormat.getDateInstance ();
DateFormat df2 = DateFormat.getDateInstance (2, Locale.FRENCH);
System.out.println ( df1.format(date)); //luna, an, zi cu luna in engleza
System.out.println ( df2.format(date)); // zi,luna,an cu luna in franceza
Clasele de obiecte fabrica pot fi la randul lor subtipuri ale unui tip comun numit fabrica abstracta ("AbstractFactory"). Altfel spus, se folosesc uneori fabrici de fabrici de obiecte.
Uneori se stie ce fel de obiecte sunt necesare, dar nu se stie exact cum trebuie realizate aceste obiecte (cum trebuie implementata clasa); fie nu exista o singura clasa posibila pentru aceste obiecte, fie existau anterior furnizori diferiti pentru aceste clase si se incearca unificarea lor, fie sunt anticipate si alte implementari ale unei clase in viitor, dar care sa nu afecteze prea mult aplicatiile existente.
Portabilitatea dorita de Java cere ca operatii (cereri de servicii) sa fie programate uniform, folosind o singura interfata API intre client (cel care solicita operatia) si furnizorul de servicii ("provider"), indiferent de modul cum sunt implementate acele servicii, sau de faptul ca exista mai multe implementari.
Astfel de interfete API exista pentru servicii prin natura lor diversificate ca mod de realizare: pentru acces la orice baza de date relationala (JDBC), pentru comunicarea prin mesaje (JMS, SAAJ), pentru analiza de fisiere XML etc.
Pentru acces la servicii oferite de un server se creeaza un obiect "conexiune", iar operatiile ulterioare se exprima prin apeluri de metode ale obiectului "conexiune". Diversitatea apare in modul de creare a obiectului conexiune, care nu se poate face prin instantierea unei singure clase, cu nume si implementare cunoscute la scrierea aplicatiei. Obiectele conexiune sunt create de fabrici de conexiuni, care ascund particularitati de implementare ale acestor obiecte. Toate obiectele conexiune trebuie sa prevada anumite metode, deci sa respecte un contract cu utilizatorii de conexiuni. Fabricile de conexiuni pot fi uneori si ele foarte diferite si sa necesite parametri diferiti (ca tip si ca utilizare); de aceea, obiectul fabrica de conexiuni nu este obtinut prin instantierea unei singure clase ci este fabricat intr-un fel sau altul.
In JDBC clasa fabrica de conexiuni, numita "DriverManager" foloseste o clasa "driver", livrata de obicei de catre furnizorul bazei de date, sub forma unei clase cu nume cunoscut (incarcata inainte de utilizarea clasei fabrica) . Exemplu:
Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver"); // clasa driver
Connection con = DriverManager.getConnection (dbURL,user,passwd);
Statement stm = con.createStatement(); // utilizare obiect conexiune
In exemplul anterior Connection si Statement sunt interfete, dar nu exista in pachetul "java.sql" nici o clasa instantiabila care sa implementeze aceste interfete. Obiectul conexiune este fabricat de metoda "getConnection" pe baza informatiilor extrase din clasa driver; la fel este fabricat si obiectul "instructiune" de catre metoda "createStatement".
Exemplu de utilizare a unei fabrici de fabrici de conexiuni pentru mesaje SOAP:
SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance();
SOAPConnection con = factory.createConnection();
.
In exemplul anterior obiectul fabrica "factory" este produs de o fabrica abstracta implicita (numele este ascuns in metoda "newInstance"), furnizata de firma "Sun" pentru a permite exersarea de mesaje SOAP, fara a exclude posibilitatea altor fabrici de conexiuni SOAP, de la diversi alti furnizori.
Pentru a intelege mai bine ce se intampla intr-o fabrica (de fabrici) de obiecte am imaginat urmatorul exemplu: o fabrica pentru crearea de obiecte colectie (de orice subtip al tipului Collection) pe baza unui vector implicit de obiecte. Exemplu:
public static Collection newCollection (Object a[], Class cls)
catch (Exception e)
for (int i=0;i<a.length;i++)
c.add (a[i]);
return c;
}
// utilizare (in "main")
String a[ ] = ;
HashSet set = (HashSet) newCollection (a,HashSet.class);
In exemplul anterior am folosit o metoda fabrica de obiecte colectie, care va putea fi folosita si pentru clase colectie inca nedefinite, dar care implementeaza interfata.
Utilizarea acestei fabrici este limitata deoarece nu permite transmiterea de parametri la crearea obiectelor colectie; de exemplu, la crearea unei colectii ordonate este necesara transmiterea unui obiect comparator (subtip al tipului Comparator).
O solutie ar putea fi definirea mai multor clase fabrica de colectii, toate compatibile cu un tip comun (tipul interfata "CollectionFactory"). Exemplu:
// interfata pentru fabrici de colectii
interface CollectionFactory
// fabrica de colectii simple (neordonate)
class SimpleCollectionFactory implements CollectionFactory catch (Exception e)
}
public Collection newCollection (Object a[])
}
// fabrica de colectii ordonate
class SortedCollectionFactory implements CollectionFactory ;
Object[] args = new Object[] ;
Constructor constr = cls.getConstructor(clsargs);
col =(Collection) constr.newInstance(args);
} catch (Exception e)
}
public Collection newCollection (Object a[])
Instantierea directa a acestor clase fabrica ar obliga utilizatorul sa cunoasca numele lor si ar necesita modificari in codul sursa pentru adaugarea unor noi clase fabrica. De aceea, vom defini o metoda statica (supradefinita) cu rol de fabrica de obiecte fabrica:
// metode fabrica de fabrici de colectii
class FFactory
public static CollectionFactory getFactory (String clsName,Comparator comp)
}
// exemple de utilizare
public static void main (String arg[]) throws Exception {
String a[] = ;
CollectionFactory cf1 = FFactory.getFactory ('java.util.HashSet');
HashSet set1 = (HashSet) cf1.newCollection (a);
CollectionFactory cf2 = FFactory.getFactory ('java.util.TreeSet', new MyComp());
TreeSet set2 = (TreeSet) cf2.newCollection (a);
System.out.println(set1); System.out.println(set2);
}
In acest caz particular se putea folosi o singura clasa fabrica de colectii cu mai multi constructori, deoarece sunt diferente mici intre cele doua fabrici. In situatii reale pot fi diferente foarte mari intre clasele fabrica pentru obiecte complexe.
Clase cu rol de comanda (actiune)
Schema numita 'Command' permite realizarea de actiuni (comenzi) multiple si realizeaza decuplarea alegerii operatiei executate de locul unde este emisa comanda. Ea foloseste o interfata generala, cu o singura functie, de felul urmator:
public interface Command
In Swing exista mai multe interfete 'ascultator' corespunzatoare interfetei "Command". Exemplu:
public interface ActionListener
Un obiect dintr-o clasa ascultator este un obiect 'comanda' si realizeaza o actiune. El constituie singura legatura dintre partea de interfata grafica a aplicatiei si partea de logica specifica aplicatiei (tratarea evenimentelor). Se realizeaza astfel decuplarea celor doua parti, ceea ce permite modificarea lor separata si chiar selectarea unei anumite actiuni in cursul executiei.
Pentru a compara solutia schemei 'Command' cu alte solutii de selectare a unei actiuni (comenzi), sa examinam pe scurt programarea unui meniu cu clase JFC. Un meniu este format dintr-o bara meniu orizontala (obiect JMenuBar), care contine mai multe optiuni de meniu (obiecte JMenu) si fiecare optiune poate avea un submeniu vertical cu mai multe elemente de meniu (obiecte JMenuItem). Un element de meniu este o optiune care declanseaza o actiune la selectarea ei, prin producerea unui eveniment de tip ActionEvent.
Pentru programarea unui meniu cu o singura optiune 'File', care se extinde intr-un meniu vertical cu doua elemente ('Open' si 'Exit') sunt necesare instructiunile:
JMenuBar mbar = new JMenuBar(); // creare bara meniu principal
setJMenuBar (mbar); // adauga bara meniu la JFrame
JMenu mFile = new JMenu ('File'); // optiune pe bara orizontala
mbar.add (mFile); // adauga optiunea mFile la bara meniu
JMenuItem open = new JMenuItem ('Open'); // optiune meniu vertical
JMenuItem exit = new JMenuItem ('Exit'); // optiune meniu vertical
mFile.add (open); mFile.addSeparator( ); // adauga optiuni la meniu vertical
mFile.add (exit);
open.addActionListener (this); // sau un alt obiect ca argument
exit.addActionListener (this); // sau un alt obiect ca argument
Tratarea evenimentelor generate de diverse elemente de meniu (mai multe surse de evenimente, in aplicatiile reale) se poate face intr-o singura metoda:
public void actionPerformed (ActionEvent e)
Aplicarea schemei 'Command', cu o interfata 'Command', conduce la definirea mai multor clase care implementeaza aceasta interfata (clase de nivel superior sau clase incluse in clasa JFrame, pentru acces la alte variabile din interfata grafica). Exemplu:
class FileExitCmd extends JMenuItem implements Command
public void execute ()
}
In programarea anterioara a meniului apar urmatoarele modificari:
class CmdListener implements ActionListener
}
. . .
ActionListener cmd = new CmdListener();
open.addActionListener (cmd);
exit.addActionListener (cmd);
Metoda 'execute' este o metoda polimorfica, iar selectarea unei implementari sau alta se face in functie de tipul variabilei "c", deci de tipul obiectului care este sursa evenimentului.
Schema 'Command' mai este folosita in programarea meniurilor si barelor de instrumente ('toolbar'), prin utilizarea obiectelor 'actiune', care implementeaza interfata Action. O bara de instrumente contine mai multe butoane cu imagini (obiecte JButton), dar are acelasi rol cu o bara meniu: selectarea de actiuni de catre operator.
In loc sa se adauge celor doua bare obiecte diferite (dar care produc aceeasi actiune) se adauga un singur obiect, de tip Action, care contine o metoda 'actionPerformed'; cate un obiect pentru fiecare actiune selectata. Interfata Action corespunde interfetei 'Command', iar metoda 'actionPerformed' corespunde metodei 'execute'.
Interfata Action extinde interfata ActionPerformed cu doua metode 'setValue' si 'getValue', necesare pentru stabilirea si obtinerea proprietatilor unui obiect actiune: text afisat in optiune meniu, imagine afisata pe buton din bara de instrumente, o scurta descriere ('tooltip') a 'instrumentului' afisat.
Clase cu un singur obiect
Uneori este nevoie de un singur obiect de un anumit tip, deci trebuie interzisa instantierea repetata a clasei respective, numita clasa 'Singleton'. Exista cateva solutii:
- O clasa fara constructor public, cu o metoda statica care produce o referinta la unicul obiect posibil ( o variabila statica si finala contine aceasta referinta).
- O clasa statica inclusa si o metoda statica (in aceeasi clasa exterioara) care instantiaza clasa (pentru a-i transmite un parametru).
Ambele solutii pot fi intalnite in clasa Collections (neinstantiabila) pentru a se crea obiecte din colectii 'speciale' : colectii vide (EmptySet, EmptyList, EmptyMap) si colectii cu un singur obiect (SingletonSet, SingletonList, SingletonMap). Exemplu:
public class Collections
private static class SingletonSet extends AbstractSet
public int size()
public boolean contains(Object o)
// public Iterator iterator()
}
. . .
}
Exemplu de utilizare in problema claselor de echivalenta :
public class Sets
. . . // metode find, union
}
In aceasta schema exista un obiect care poate suferi diverse modificari (subiectul observat) si unul sau mai multe obiecte observator, care ar trebui anuntate imediat de orice modificare in obiectul observat, pentru a realiza anumite actiuni.
Obiectul observat contine o lista de referinte la obiecte observator si, la producerea unui eveniment, apeleaza o anumita metoda a obiectelor observator inregistrate la el.
Relatia dintre o clasa JFC generator de evenimente si o clasa ascultator (receptor) care reactioneaza la evenimente este similara relatiei dintre o clasa observata si o clasa observator. De exemplu, un buton JButton are rolul de obiect observat, iar o clasa care implementeaza interfata ActionListener si contine metoda "actionPerformed" are rolul de observator.
Schema observat-observator a generat clasa Observable si interfata Observer din pachetul 'java.util'. Programatorul de aplicatie va defini una sau mai multe clase cu rol de observator, compatibile cu interfata Observer. Motivul existentei acestei interfete este acela ca in clasa Observable (pentru obiecte observate) exista metode cu argument de tip Observer pentru mentinerea unui vector de obiecte observator. Exemplu:
public void addObserver(Observer o)
Interfata Observer contine o singura metoda 'update', apelata de un obiect observat la o schimbare in starea sa care poate interesa obiectele observator inregistrate anterior:
public interface Observer
Clasa Observable nu este abstracta dar nici nu este direct utilizabila; programatorul de aplicatie va defini o subclasa care preia toate metodele clasei Observable, dar adauga o serie de metode specifice aplicatiei care apeleaza metodele superclasei "setChanged" si "notifyObservers". Metoda "notifyObservers" apeleaza metoda 'update' pentru toti observatorii introdusi in vectorul 'observers':
public class Observable
public void notifyObservers(Object arg)
protected void setChanged()
. . .
Exemplu de definire a unei subclase pentru obiecte observate:
class MyObservable extends Observable
}
Clasa observator poate arata astfel, daca metoda "update" adauga cate un caracter la o bara afisata pe ecran (la fiecare apelare de catre un obiect MyObservable).
class ProgressBar implements Observer
}
Exemplu de utilizare a obiectelor observat-observator definite anterior:
public static void main(String[ ] av)
Interfetele ActionListener, ItemListener s.a. au un rol similar cu interfata Observer, iar metodele 'actionPerformed' s.a. corespund metodei 'update'. Programatorul de aplicatie defineste clase ascultator compatibile cu interfetele 'xxxListener', clase care implementeaza metodele de tratare a evenimentelor ('actionPerformed' s.a).
Corespondenta dintre metodele celor doua grupe de clase este astfel:
addObserver addActionListener, addChangeListener
notifyObservers fireActionEvent, fireChangeEvent
update actionPerformed, stateChanged
Clase "model" in schema MVC
Schema MVC (Model-View-Controller) este o extindere a schemei 'Observat-observator'. Un obiect "model" este un obiect observat (ascultat), care genereaza evenimente pentru obiectele receptor inregistrate la model, dar evenimentele nu sunt produse ca urmare directa a unor cauze externe programului (nu sunt cauzate direct de actiuni ale operatorului uman).
Arhitectura MVC foloseste clase avand trei roluri principale:
- Clase cu rol de "model", adica de obiect observat care contine si date.
- Clase cu rol de redare vizuala a modelului, adica de obiect observator al modelului.
- Clase cu rol de comanda a unor modificari in model.
Schema MVC a aparut in legatura cu programele de birotica (pentru calcul tabelar si pentru editare de texte), de unde si numele de "Document-View-Controller".
Separarea neta a celor trei componente (M, V si C) permite mai multa flexibilitate in adaptarea si extinderea programelor, prin usurinta de modificare separata a fiecarei parti (in raport cu un program monolit la care legaturile dintre parti nu sunt explicite). Astfel, putem modifica structurile de date folosite in memorarea foii de calcul (modelul) fara ca aceste modificari sa afecteze partea de prezentare, sau putem adauga noi forme de prezentare a datelor continute in foaia de calcul sau noi forme de interactiune cu operatorul (de exemplu, o bara de instrumente "tool bar").
Separarea de responsabilitati oferita de modelul MVC este utila pentru realizarea de aplicatii sau parti de aplicatii: componente GUI, aplicatii de tip "client-server" s.a.
Pentru a ilustra folosirea schemei MVC in proiectarea unei aplicatii si afirmatia ca introducerea de obiecte (si clase) suplimentare face o aplicatie mai usor de modificat vom considera urmatoarea problema simpla:
Operatorul introduce un nume de fisier, aplicatia verifica existenta acelui fisier in directorul curent si adauga numele fisierului la o lista de fisiere afisata intr-o alta fereastra. Ulterior vom adauga o a treia fereastra in care se afiseaza dimensiunea totala a fisierelor selectate. Daca nu exista fisier cu numele primit atunci se emite un semnal sonor si nu se modifica lista de fisiere (si nici dimensiunea totala a fisierelor).
Obiectele folosite de aplicatie pot fi : JTextField pentru introducerea unui nume de fisier, JTextField pentru afisare dimensiune totala si JTextArea pentru lista de fisiere. Ele vor avea atasate si etichete JLabel care explica semnificatia ferestrei. Pentru a avea mai multa libertate in plasarea ferestrelor pe ecran vom crea doua panouri.
Fereastra de introducere are rolul de "controler" deoarece comanda modificarea continutului celorlalte doua ferestre, care sunt doua "imagini" diferite asupra listei de fisiere selectate.
Secventa urmatoare realizeaza crearea obiectelor necesare si gruparea lor si este comuna pentru toate variantele discutate pentru aceasta aplicatie.
class MVC extends JFrame
public static void main(String args[])
}
Programul este utilizabil, dar poate fi extins pentru "slefuirea" aspectului ferestrei principale cu chenare pe componente, cu intervale de separare ("strouts") si intre ferestre si cu o alta grupare a componentelor atomice Swing in panouri.
In varianta aplicatiei cu numai doua ferestre (cu un singur obiect "imagine") legarea obiectului "listView" la obiectul "controler" se face prin inregistrarea unui obiect ascultator la sursa de evenimente de tip ActionEvent si prin implementarea metodei "actionPerformed" cu actiunea declansata in obiectul receptor:
// Legare listView la controler
controler.addActionListener( new ActionListener()
});
In secventa anterioara s-a definit o clasa inclusa anonima cu rol de "adaptor" intre controler si imagine. Continutul clasei adaptor depinde si de tipul componentei de comanda (aici un camp text) si de rolul componentei imagine in aplicatie.
Pentru extinderea acestei aplicatii cu afisarea dimensiunii totale a fisierelor introduse pana la un moment dat avem mai multe solutii:
a) Adaugarea unui nou receptor la "controler" printr-o alta clasa adaptor intre controler si noul obiect "imagine". De remarcat ca va trebui sa repetam o serie de operatii din adaptorul deja existent, cum ar fi verificarea existentei fisierului cu numele introdus in campul text. Mai neplacut este faptul ca succesiunea in timp a executiei metodelor "actionPerformed" din cele doua obiecte adaptor (receptor) nu este previzibila. De fapt aplicatia functioneaza corect numai daca este adaugat mai intai adaptorul pentru campul text "sizeView" si apoi adaptorul pentru zona text "listView" (ordinea inversa modifica comportarea aplicatiei):
// Legare controler la sizeView
controler.addActionListener( new ActionListener()
else
Toolkit.getDefaultToolkit().beep();
}
});
// Legare controler la listView
controler.addActionListener( new ActionListener()
});
b) Modificarea adaptorului astfel ca sa lege obiectul "controler" la ambele obiecte imagine, actualizate intr-o singura functie apelata la declansarea evenimentului de modificare a continutului campului text "controler":
// Legare controler la sizeView si listView
controler.addActionListener( new ActionListener()
else
Toolkit.getDefaultToolkit().beep();
controler.setText(''); // sterge cimp de intrare
}
});
Desi este forma cea mai compacta pentru aplicatia propusa, modificarea sau extinderea ei necesita modificari intr-o clasa existenta (clasa adaptor), clasa care poate deveni foarte mare daca numarul obiectelor ce comunica si/sau functiile acestora cresc. Din acest motiv clasa adaptor este mai bine sa fie o clasa cu nume, definita explicit, chiar daca ramane inclusa in clasa MVC.
Dintre modificarile posibile mentionam : adaugarea unor noi obiecte imagine (de exemplu o fereastra cu numarul de fisiere selectate si o bara care sa arate proportia de fisiere selectate din fisierul curent ) si inlocuirea campului text din obiectul controler cu o lista de selectie (ceea ce este preferabil, dar va genera alte evenimente si va necesita alte metode de preluare a datelor).
c) Cuplarea obiectului "sizeView" la obiectul "listView" sau invers nu este posibila, pentru ca un camp text sau o zona text nu genereaza evenimente la modificarea lor prin program ci numai la interventia operatorului (evenimentele sunt externe si asincrone programului in executie).
In variantele prezentate aplicatia nu dispune de datele introduse intr-un obiect accesibil celorlalte parti din aplicatie, desi lista de fisiere ar putea fi necesara si pentru alte operatii decat afisarea sa (de exemplu, pentru comprimare si adaugare la un fisier arhiva). Extragerea datelor direct dintr-o componenta vizuala Swing nu este totdeauna posibila si oricum ar fi diferita pentru fiecare tip de componenta.
Introducerea unui nou obiect cu rol de "model" care sa contina datele aplicatiei (lista de fisiere validate) permite separarea celor trei parti din aplicatie si modificarea lor separata. Pentru problema data se poate folosi modelul de lista DefaultListModel, care contine un vector (si suporta toate metodele clasei Vector), dar in plus poate genera trei tipuri de evenimente: adaugare element, eliminare element si modificare continut. Sau, putem defini o alta clasa care implementeaza interfata ListModel .
Un receptor al evenimentelor generate de un model ListModel trebuie sa implementeze interfata ListDataListener, care contine trei metode : "intervalAdded", "intervalRemoved" si "contentsChanged". Se poate defini o clasa adaptor cu toate metodele interfetei ListDataListener, dar fara nici un efect ("ListDataAdapter").
Acum sunt necesare trei clase adaptor care sa lege la model obiectul controler si cele doua obiecte imagine, dintre care doua extind clasa "ListDataAdapter":
DefaultListModel model = new DefaultListModel(); // obiect "model"
. . .
// adaptor intre controler si model
class CMAdapter implements ActionListener
}
// clase adaptor intre model si imaginile sale
class MV1Adapter extends ListDataAdapter
}
class MV2Adapter extends ListDataAdapter
}
. . .
// Cuplare obiecte MVC prin obiecte adaptor
controler.addActionListener( new CMAdapter()); // model la controler
model.addListDataListener( new MV1Adapter()); // sumView la model
model.addListDataListener( new MV2Adapter()); // listView la model
}
O clasa 'adaptor' are rolul de adaptare a unui grup de metode (o 'interfata') la un alt grup de metode si reprezinta o alta schema de proiectare.
Desi textul sursa al aplicatiei a crescut substantial fata de varianta monolit, extinderea sau modificarea aplicatiei a devenit mai simpla si mai sigura pentru ca se face prin prin adaugarea de noi obiecte sau prin inlocuirea unor obiecte, fara a modifica obiectele existente sau metodele acestor obiecte.
Aplicatia se poate imbunatati prin modificarea modului de alegere a fisierelor de catre operator: in loc ca acesta sa introduca mai multe nume de fisiere (cu greselile inerente) este preferabil ca aplicatia sa afiseze continutul unui director specificat iar operatorul sa selecteze fisierele dorite.
Schema MVC este mult folosita in realizarea aplicatiilor Web, unde partea de model contine clasele ce corespund tabelelor bazei de date (inclusiv logica de utilizare a acestor date), partea de vizualizare corespunde interfetei cu utilizatorul (obiecte grafice afisate de aplicatie in browser, validari asupra datelor introduse de operator, animatie si alte efecte vizuale), iar partea de comanda ("controller") corespunde preluarii cererilor HTTP si transmiterii raspunsurilor corespunzatoare (care include de obicei date din model ).
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1349
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved