Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml


Proiectare orientata pe obiecte - Analiza si proiectarea orientate pe obiecte

java



+ Font mai mare | - Font mai mic



Proiectare orientata pe obiecte

Analiza si proiectarea orientate pe obiecte



Aplicatiile tind sa devina tot mai complexe si mai distribuite, astfel ca faza care precede scrierea de cod devine tot mai importanta. Aceasta faza cuprinde analiza cerintelor si a cazurilor de utilizare ("use cases"), plus proiectarea la nivel abstract a structurii software. Rezultatele proiectarii se exprima de obicei in diverse diagrame UML (diagrame statice de clase, diagrame de obiecte, diagrame de evenimente, s.a.).

Programarea orientata obiect este o paradigma de descompunere a codului folosind clase si obiecte in loc de proceduri (functii). Analiza orientata obiect modeleaza aplicatia printr-un sistem de obiecte care colaboreaza intre ele, fiecare avand responsabilitati distincte. Rezultatul analizei este un model conceptual, format din concepte specifice domeniului modelat, cu relatii intre aceste concepte (obiecte conceptuale). Mai exact, este vorba de concepte si de relatii stabile, care nu se vor modifica odata cu modificarea cerintelor.

Proiectarea orientata obiect completeaza modelul conceptual furnizat de analiza cu restrictii de implementare impuse de limbajul folosit , de instrumentele software folosite si de considerente de arhitectura a sistemului. Conceptele devin interfete, clase, obiecte si relatii dintre ele. Conceptele stabile pot deveni servicii reutilizabile, iar conceptele instabile pot deveni clase algoritmice, care iau decizii sau tin seama de contextul aplicatiei.

Distribuirea de responsabilitati intre clase (interfete) se poate face in mai multe feluri (dupa diferite criterii), ceea ce a condus, de exemplu, la solutii diferite pentru modelul obiect al unui infoset XML: DOM, JDOM, DOM4J, XOM, (AXI)OM, s.a.

S-au propus si se folosesc in faza de proiectare diverse instrumente software care sa faciliteze identificarea si specificarea claselor (de exemplu, CRC=Class- Responsability-Collaboration-Card), vizualizarea relatiilor dintre obiecte si clase, precum si a secventei de evenimente importante din sistem (UML= Unified Modelling Language), s.a.

Clasele si obiectele necesare intr-o aplicatie pot rezulta din:

- Analiza aplicatiei concrete si modelarea obiectelor si actiunilor din aplicatie;

- Bibliotecile standard Java;

- Folosirea unor solutii de proiectare deja folosite cu succes in alte aplicatii.

In general se poate afirma ca marirea flexibilitatii in extinderea si adaptarea unei aplicatii se poate obtine prin marirea numarului de clase si obiecte din aplicatie. Un proiect ce contine numai clasele rezultate din analiza aplicatiei poate fi mai compact, dar nu este scalabil si adaptabil la alte cerinte aparute dupa realizarea prototipului aplicatiei. In plus, este importanta o separare a partilor susceptibile de schimbare de partile care nu se mai modifica si evitarea cuplajelor 'stranse' dintre clase.

Un program si modulele sale componente trebuie proiectate de la inceput avand in vedere posibilitatea de a fi extins si adaptat ulterior intr-un mod cat mai simplu si mai sigur (proiectare in perspectiva schimbarii = "design for change"). In plus, trebuie avuta in vedere si reutilizarea unor clase din aplicatie in alte aplicatii inrudite.

Un sistem software bine proiectat este usor de inteles, usor de mentinut si usor de refolosit (parti din el in alte aplicatii). In acest scop relatiile de dependenta dintre clase trebuie formulate explicit prin interfete bine definite si stabile, chiar daca anumite clase care implementeaza aceste interfete se vor modifica in timp.

O clasa trebuie sa aiba o singura responsabilitate si deci sa existe un singur motiv pentru modificarea ei. Din acest motiv clasele Java nu contin metode pentru scrierea datelor clasei in fisiere sau pentru conversia datelor in XML; orice modificare in procedurile de lucru cu suportul extern sau in modelul XML ar necesita modificarea acestor clase care ar reuni mai multe responsabilitati.

Principiul "open-close" sustine ca o clasa trebuie sa fie deschisa pentru extindere dar inchisa pentru modificari. Altfel spus, modificarea contextului in care se foloseste o clasa nu trebuie sa conduca si la modificarea clasei.

Un alt principiu spune ca utilizatorul unui tip clasa trebuie sa poata folosi o subclasa derivata, fara sa stie nimic in plus despre acea subclasa si fara a folosi operatorul instanceof pentru a diferentia intre diferite subtipuri. Daca o anumita metoda din clasa de baza nu are sens pentru o subclasa, atunci se va arunca o exceptie de operatie nepermisa in subclasa.

Un alt principiu este acela ca module de pe un nivel superior nu trebuie sa depinda de module de pe un nivel inferior, ci ambele vor depinde de abstractii. Mai precis, nici o clasa nu trebuie sa depinda de o clasa concreta volatila; daca definim o clasa derivata atunci vom deriva dintr-o clasa abstracta, daca o clasa contine o referinta la o alta clasa, atunci aceasta va fi o clasa abstracta sau o interfata.

Beneficiarii unei clase nu trebuie sa depinda de metode pe care nu le folosesc si care s-ar putea modifica; astfel de situatii pot apare in cazul unor clase mari, cu multe metode, pentru diversi utilizatori ai clasei. Solutia este fie impartirea in clase mai mici sau definirea de interfete pentru o parte din metodele clasei, destinate diferitelor categorii de utilizatori ai clasei.

Un programator cu experienta poate "simti" erori sau inabilitati de proiectare ("design smells") privind codul sursa. Printre atributele unui cod sursa prost proiectat sunt: rigiditatea (incercarea de a modifica ceva antreneaza multe alte modificari si in alte parti), fragilitatea (modificarea unei parti de cod face sa nu mai lucreze corect alte parti de cod), imobilitatea (dificultatea de a extrage din program parti care sa poata fi utilizate si in alte aplicatii), opacitatea (intentiile nu sunt reflectate bine in cod), repetarea inutila a unor secvente de cod si complexitatea inutila (facilitati nefolosite in prezent dar care vizeaza dezvoltari ulterioare).

Un cod sursa rezultat dintr-o buna proiectare este simplu, compact, usor de inteles, usor de testat si usor de intretinut.

Proiectarea este de obicei un proces iterativ, de imbunatatire treptata a solutiilor, chiar daca nu vedem decat rezultatul final al acestui proces.

Proiectarea unei aplicatii Java de traversare directoare

Exemplul ales urmareste sa ilustreze cateva probleme tipice care apar la scrierea unei aplicatii (relativ simple) cu obiecte:

- Codificarea trebuie precedata de o etapa de analiza si de proiectare, pentru ca intotdeauna exista mai multe solutii diferite pentru o problema.

- Cunoasterea solutiilor folosite in alte probleme, deci a unor "scheme de proiectare" confirmate de practica este utila de multe ori.

- Dezvoltarea iterativa, pornind de la o varianta initiala simpla la care se adauga treptat alte functii, este posibila de multe ori in abordarea unor probleme mai complexe.

Au fost scrise si publicate o serie de programe care au in comun prelucrarea fisierelor dintr-un director si din subdirectoarele sale, necesare in aplicatii cum ar fi: afisarea selectiva a unora dintre aceste fisiere, cautarea fisierelor care satisfac anumite conditii (exprimate eventual prin expresii regulate), arhivarea fisierelor gasite intr-un singur fisier comprimat, indexarea grupului de fisiere in vederea unei cautari rapide (folosind un produs cum este biblioteca de clase Lucene, disponibila pe site-ul Apache). Aceste programe sunt fie comenzi sistem sau utilitare independente ("dir" sau "ls", "find", "grep", "pkzip" s.a.), fie parti din aplicatii cu interfata grafica ("WinZip", optiunea "Search" din aplicatia "MyComputer" din Windows XP, s.a.)

Toate aceste programe contin o functie recursiva de parcurgere a unui sistem ierarhic de fisiere, care difera insa prin operatiile aplicate asupra fisierelor gasite. De obicei sunt verificate daca indeplinesc conditiile si prelucrate numai fisierele normale, nu si fisierele subdirector (toate directoarele trec de filtre). Parcurgerea este programata explicit si pentru fiecare fisier este apelata o functie diferita de la aplicatie la aplicatie (functie "callback" incorporata intr-un obiect functional), functie numita uneori "aplicator" si care primeste ca argument fisierul curent.

In principiu ar fi posibila si utilizarea unui iterator pe arborele de fisiere, la care metoda "next" ar furniza urmatorul fisier intr-o parcurgere prefixata (in adancime) a arborelui de fisiere. Un astfel de iterator exista in clasa DefaultMutableTreeNode sub forma metodei "depthFirstEnumeration" dar pentru un arbore cu legaturi explicite intre noduri. Este demn de remarcat ca la nivelul limbajului C (la nivelul sistemului de operare) se foloseste un astfel de iterator cu doua functii "findFirst" si "findNext", iterator folosit de metoda "list" din clasa Java File. Programarea acestui iterator este mai dificila decat scrierea functiei recursive care foloseste metoda "list".

Exemplu de clasa cu o functie de afisare a fisierele dintr-un director dat (si din subdirectoare) care satisfac o conditie implementata ca un filtru:

public class Finder

public Finder (String d)

// singura metoda publica a clasei

public void find ()

// functie recursiva de parcurgere directoare

private void findrec (File dir)

}

}

// exemple de utilizare

new Finder('C:/Sun').find(); // cautare fara filtru intr-un director

new Finder('..', new SizeFilter(1000)).find(); // cautare cu filtru

Numele directorului initial putea fi transmis ca argument metodei "find" si nu constructorului clasei Finder. Mai importanta insa este observatia ca evitarea aplicarii filtrului asupra directoarelor se va face in clasa filtru si nu in metoda "find", deci cade in sarcina aplicatiei. Daca se aplica un filtru de forma "se accepta numai fisierele cu dimensiune peste o valoare data" asupra tuturor fisierelor, atunci nu se intra in subdirectoare doarece toate fisierele director au lungimea zero. Exemplu de filtru:

class SizeFilter implements FileFilter

public boolean accept (File f)

}

Anumite aplicatii pot fi interesate de momentele in care se intra (si se iese) dintr-un subdirector; de exemplu, un program de afisare cu indentare va modifica indentarea la aceste momente. De aceea, evenimentele care trebuie notificate observatorului sunt trei: intrarea intr-un nou director (schimbarea directorului curent), gasirea unui nou fisier (normal) si iesirea din directorul curent.

Vom adauga acum functiei recursive "findrec" evitarea filtrarii directoarelor si semnalarea intrarii si iesirii din subdirectoare.

private void findrec (File f)

endDir(f); // iesire din director

}

}

// prelucrare fisier gasit

public void foundFile (File file)

// operatii la schimbare director

public void startDir (File file)

public void endDir (File file)

Ultimele trei metode nu trebuie sa faca parte din clasa Finder; ele trebuie sa fie metode callback, parte din aplicatia care prelucreaza fisierele gasite (prin afisare, arhivare, indexare, etc.).

Separarea algoritmului de traversare (parcurgere) de operatia aplicata asupra fisierelor gasite permite reutilizarea acestui algoritm intr-o diversitate de aplicatii, care pot fi folosite atat in modul text (linie de comanda) cat si cu interfata grafica. In plus, este izolata partea din aplicatie care nu se mai modifica (procesul de cautare in arborele de subdirectoare) de partea susceptibila de modificare (modul de folosire a rezultatelor cautarii, obtinerea unor optiuni de cautare, s.a).

Pentru evitarea unor cautari de durata se poate specifica o adancime maxima de subdirectoare in care se cauta sau posibilitea de intrerupere fortata a cautarii de catre utilizator.

Optiunile (conditiile) de cautare se pot transmite sub forma unui dictionar cu nume de optiuni si valori ale acestora, dictionar completat fie pe baza argumentelor din linia de comanda, fie pe baza datelor introduse de operator in componente vizuale (campuri text sau alte obiecte grafice).

Problema seamana cu o schema "observat-observator", in care obiectul observat este cel care viziteaza sistemul de fisiere (cu metoda recursiva), iar obiectul observator este specific aplicatiei si este notificat la fiecare schimbare de stare in obiectul observat, adica la fiecare nou fisier gasit.

Solutii posibile pentru clasa Finder

Putem observa o asemanare intre fisierele gasite de acest utilitar si elementele extrase dintr-un fisier XML de catre un parser XML (numar posibil mare de elemente si structura ierarhica) si deci ne putem inspira din modul in care diverse programe parser XML transmit aplicatiilor informatiile despre atomii XML gasiti intr-un fisier. Asemanarea consta in aceea ca functia de traversare a sistemului de fisiere (corespunde unei functii "parse") stie sa gaseasca fisiere dar nu stie ce sa faca cu fisierele gasite; de aceea semnaleaza aplicatiei evenimentele importante si transmite acesteia numele fisierelor sau subdirectoarelor gasite.

Putem distinge doua categorii mari de solutii:

- Crearea unui arbore cu fisierele gasite sau unei liste de cai la fisiere, dupa modelul DOM sau dupa modelul XPath.

- Apelarea de metode "callback" din aplicatie, dupa modelul SAX, sau generarea de evenimente specifice la gasirea fiecarui fisier, intrarea si iesirea dintr-un director, dupa modelul evenimentelor generate de componentele grafice Swing (cu ascultatori la evenimente). Aceste solutii au avantajul ca evita un consum mare de memorie in cazul unui numar foarte mare de fisiere gasite.

Generarea de evenimente si utilizarea de clase ascultator are ca avantaj posibilitatea existentei mai multor ascultatori, deci realizarea mai multor operatii diferite la producerea unui eveniment (cum ar fi arhivarea fisierelor gasite si afisarea lor intr-un obiect JTree).

Pentru a reduce dimensiunea programului nostru si a-l face mai usor de inteles vom folosi modelul SAX, cu metode 'callback' apelate de 'find' si definite de aplicatie. Si cu aceasta alegere avem (cel putin) doua variante:

- Metodele 'callback' grupate intr-o interfata, care trebuie implementata de o clasa a aplicatiei, iar o referinta la aceasta clasa este transmisa metodei 'find' (sau la construirea unui obiect ce contine metoda 'find');

- Metodele 'callback' fac parte din aceeasi clasa (abstracta) cu metoda 'find' (ca metode abstracte sau cu efect nul), iar un utilizator trebuie sa-si defineasca o clasa derivata, in care sa implementeze si metodele 'callback'.

Solutia clasei abstracte, care include si metodele ce apeleaza metodele 'callback', nu se foloseste pentru un parser XML (SAX) deoarece un program parser este compus din multe clase si metode si nu este eficient sa fie inclus in orice aplicatie care-l foloseste. In cazul nostru metoda "find' si cele cateva mici metode auxiliare pot fi incluse fara costuri intr-o aplicatie ce implica cautarea de fisiere.

Vom prefera o clasa neinstantiabila, cu metode avand efect nul si nu cu metode abstracte, deoarece nu toate trebuie redefinite in clasa derivata (aplicatia care foloseste metoda find este o clasa derivata):

abstract class Finder

protected void foundFile(File file)

protected void endDir(File dir)

}

Ca exemplu de utilizare vom realiza o afisare indentata a numelor de fisiere, inclusiv nume de directoare, fara filtrare:

class FileLister extends Finder

// metode callback redefinite

protected void foundFile(File file)

protected void startDir(File dir)

protected void endDir(File dir)

public static void main (String args[])

}

In varianta cu interfata, aceasta va contine numai trei metode callback:

public interface FileHandler

Clasa Finder se modifica pentru a primi un obiect care implementeaza interfata FileHandler:

private FileHandler h; // variabila a clasei Finder

public void find (FileHandler h)

// cautare recursiva

private void findrec (File f)

h.endDir(f); // apel metoda din interfata

}

}

Extinderea clasei Finder

Vom extinde acum clasa Finder cu anumite optiuni de cautare transmise printr-un dictionar fie ca argument al metodei 'find', fie al constructorului clasei. Aceste optiuni vor fi transformate in filtre individuale de tipul FileFilter si apoi intr-un singur filtru combinat, de acelasi tip, pentru a fi folosit de catre metoda "listFiles". Ne vom rezuma la trei criterii de cautare uzuale: dupa nume (cu sabloane "wildcards"), dupa data ultimei modificari a fisierului si dupa dimensiunea fisierului. Pentru ultimele doua criterii se da o limita, iar conditia de acceptare a unui fisier este ca data (lungimea) sa fie mai mica sau mai mare ca aceasta limita. In practica se folosesc mai multe criterii: fisiere cu anumite atribute: Hidden, Read-Only, fisiere care contin un anumit sir, s.a.

Ca exemplu simplu de utilizare vom da un program de afisare a cailor la fisierele care satisfac simultan anumite conditii, in care se va defini numai metoda "foundFile". In exemplu se creeaza dictionarul de optiuni in functia "main", dar intr-o aplicatie reala optiunile sunt extrase fie din linia de comanda, fie din campuri text sau alte componente vizuale de interfata grafica.

class FileLister extends Finder

protected void foundFile(File file)

public static void main (String args[])

}

Rezulta ca a fost modificata si clasa Finder astfel ca sa primeasca un argument de tip Map in locul unui argument de tipul FileFilter:

abstract class Finder

. // in rest la fel ca in clasa abstracta Finder data anterior

Pentru satisfacerea simultana a mai multor criterii vom defini o clasa auxiliara pentru filtre combinate care foloseste o lista de filtre:

public class ComboFilter implements FileFilter

public void addFilter( FileFilter filter)

public boolean accept( File file)

}

return true; // daca file a trecut de toate filtrele

}

}

Clasa IOFilter, derivata din ComboFilter, creeaza lista de filtre:

class IOFilter extends ComboFilter

}

}

// selectarea unui filtru dupa numele optiunii

private FileFilter selectFilter(final String opt,final Object val)

}

Urmeaza clasele pentru filtre individuale:

// filtru dupa lungime fisier

public class SizeFilter implements FileFilter // implicit la mai mare

public SizeFilter(Long size, boolean more)

this.size = size; this.more = more;

}

public boolean accept(File file)

}

// filtru dupa data ultimei modificari a fisierului

public class DateFilter implements FileFilter // implicit "after"

public DateFilter(Date limit, boolean after)

public boolean accept(File file)

}

// filtru dupa nume (potrivire cu un sablon ce poate contine '*' si '?')

public class WildcardFilter implements FileFilter

public WildcardFilter(String wildcard, boolean caseSensi)

public boolean accept(File file)

return wildMatch(wildcard, name);

}

// daca sirul "str" se potriveste cu sablonul "pat" (pattern)

private boolean wildMatch ( String pat, String str)

else if (pat.charAt(0) == '?') else if (pat.charAt(0) == '*')

while (pat.length()>0 && pat.charAt(0) == '*');

if (pat.length()==0) return true;

while (str.length()>0)

if (wildMatch(pat, str=str.substring(1))) return true;

return false;

} else

}

while (pat.length()>0 && pat.charAt(0) == '*')

pat=pat.substring(1);

return pat.length()==0;

}

Alte exemple de utilizare a clasei Finder

Pentru a verifica cat de versatila este clasa Finder vom incerca sa o folosim in doua aplicatii mai complexe: o aplicatie cu interfata grafica pentru afisarea unui arbore de fisiere si un arhivator.

Vom defini mai intai o clasa derivata din clasa abstracta Finder pentru crearea unui arbore de fisiere, care va putea fi folosit fie in aplicatii cu interfata grafica, fie in aplicatii cu interfata text:

// clasa derivata pentru afisare cai la fisiere gasite (fara indentare)

class FileTree extends Finder

public FileTree (String dir) // constructor fara optiuni

// redefinirea metodei apelate la fiecare fisier gasit

protected void foundFile(File file)

public TNode getRoot ()

}

Nodurile arborelui ar fi putut contine obiecte String cu numele fisierelor in loc de obiecte File, dar atunci era mai complicat sa aflam nodul parinte. In plus, ar fi fost posibil sa existe mai multe noduri cu acelasi continut (fisiere cu acelasi nume in directoare diferite); un obiect File contine calea completa la un fisier, care difera chiar pentru fisiere cu acelasi nume din directoare diferite.

Clasa pentru interfata grafica foloseste un obiect JTree pentru afisare, campuri text pentru preluare nume director, masca de selectie fisiere si dimensiune limita fisiere, obiect JComboBox pentru alegere

mod de utilizare limita (ca limita inferioara sau ca limita superioara) si un buton care comanda citirea datelor din campuri text si afisarea arborelui cu fisierele selectate conform optiunilor:

class FinderGUI extends JFrame implements ActionListener

// initializare componente

private void initComponents() ; // afisate in JComboBox

folder = new JTextField(10); // creare camp text director

folder.setText('.'); // initial afisare din directorul curent

name = new JTextField(10); // creare camp text masca nume fisiere

name.setText('*.*'); // initial toate fisierele

size= new JTextField(10); // creare camp text ptr limita dimensiune

size.setText('0'); // initial limita inferioara zero

mm=new JComboBox(mmopt); // creare obiect selectie

start = new JButton('Start'); // creare buton

start.addActionListener(this); // ascultator la actionare buton

FileTree ft=new FileTree('.'); // creare arbore afisat initial

jtree= new JTree(new DefaultTreeModel (ft.getRoot()));

}

// plasare obiecte vizuale pe ecran

private void setLayout()

// ascultator la eveniment produs de buton

public void actionPerformed (ActionEvent ev)

public static void main (String args[])

}

Scrierea unui program de arhivare in format "zip" este facilitata de existenta clasei de biblioteca ZipOutputStream din pachetul java.util.zip. Metoda "putNextEntry" scrie antetul care precede fiecare fisier in arhiva, iar metoda "write" scrie datele din fisierul arhivat in arhiva (un bloc de octeti). Pentru ca arhiva sa aiba structura sistemului de fisiere arhivat (astfel ca la dezarhivare sa se refaca structura de directoare si fisiere) este suficient sa dam metodei "putNextEntry" calea completa la fisier si nu doar numele fisierului arhivat. Exemplu de metoda statica pentru arhivarea fisierelor dintr-un director care nu contine si subdirectoare:

public static void zipFolder(String dir, String outFile)

in.close(); out.close();

}

catch(Exception e)

}

Daca directorul "dir" contine si subdirectoare atunci apar exceptii la incercarea de citire date din aceste subdirectoare. Metoda "foundFile" din clasa "Finder" este apelata atat pentru fisiere normale cat si pentru subdirectoare, de aceea vom testa daca este sau nu director pentru a evita exceptiile"

class FinderZip extends Finder catch(Exception e)

super.find();

try catch(Exception e)

}

protected void foundFile(File file) catch(Exception e)

}

}

Exemplu de utilizare:

new FinderZip ('F:/groovy-1.5.4', 'C:/test.zip');



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1212
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved