Upload
others
View
13
Download
0
Embed Size (px)
Citation preview
Design Patterns TEMA – INGINERIE SOFTWARE
Studenți: Mihai Octavian 441A
Diaconescu Radu 441A
Sălăjan Alexandru 441A
Universitatea Politehnica București
Facultatea de Electronică, Telecomunicații și Tehnologia Informației
București - 2015
1
Curpins:
1. Introducere……………………………………………………………………………..2
2. Creational Patterns (Salajan Alexandru) ...…………………….………..3
2.1 Abstract factory pattern………………………………………….…………3
2.2 Factory Method pattern……………………………………….……………8
2.3 Prototype pattern…………………………………………………………….10
2.4 Singleton pattern……………………………………………………………..13
3. Behavioral patterns (Diaconescu Radu) …………………….…………..14
3.1 Introducere……………………………………………………….……………..15
3.2 Strategy pattern……………………………………………………………….15
3.3 Template Method pattern………………………………………………..18
3.4 Iterator pattern ………………………………………………………….…….21
3.5 Command pattern…………………………………………….………………24
4. Structural Patterns (Mihai Octavian) ……………………….……………..28
4.1 Adapter pattern………………………………………………..….……………29
4.2 Bridge Design pattern……………………………………..….……………..33
4.3 Composite pattern…………………………………………………………….36
4.4 Decorator pattern……………………………………………………………..40
5. Bibliografie…………………………………………………………..………………….43
2
1.Introducere:
În ingineria software, un model de design (design pattern) reprezintă o soluție a unei probleme
ce apare de obicei apare în design-ul de software, ce poate fi reutilizabilă în mod general. Un model de
design nu este neapărat un design ce poate fi implementat direct în cod. El reprezintă o descriere sau un
șablon pentru modul de a rezolva o problem, permițând folosirea sa în mai multe situații diferite.
Modele de design obiect orientate referă de obicei relațiile și interacțiunile dintre clase sau obiecte, fără
a specifica clasele de aplicații finale sau obiectele care sunt implicate.
Modele de design se regăsesc în module și interconexiuni. La un nivel mai ridicat există însă
modele arhitecturale ce sunt caracterizate de prezența lor în domeniul de aplicare, de obicei, acestea
descriind un model global, urmat de un întreg sistem.
Există mai multe tipuri ale acestor modele de design: Structural patterns(modelul structural) ce
răspund de preocupările legate de structura la nivel înalt a unei aplicații în curs de dezvoltare. Un
behavioral pattern(model comportamental) explică modul în care obiectele interacționează.
Acesta descrie modul în care diferite obiecte și clase trimit mesaje unul altuia pentru a face
lucrurile să se întâmple și cum etapele unei sarcini sunt împărțite între diferite obiecte.
Împărțirea etapelor se face prin folosirea moștenirii. Creational patterns (modelele creationale)
se referă în general la instanțierea claselor și pot fi împărțite în modele de creare a
claselor(class-creation patterns) și modele de creare a obiectelor(object-creational patterns) .
Model-ul de creare a claselor folosește moștenirea în procesul de instanțiere, iar model-ul de
creare a obiectelor va delega instanțierea unui alt obiect.
Modele de proiectare pot accelera procesul de dezvoltare prin furnizarea de cunoștințe
și ipoteze testate și acceptate. Proiectarea eficienta a unui software necesită acoperirea tutor
problemelor, chiar și a celor ce pot deveni vizibile în etape mai târzii de dezvoltare a aplicației.
Reutilizarea modelelor de design ajută la prevenirea problemelor subtile care se pot transforma
în majore și îmbunătățesc citirea codului pentru programatori și arhitecții familiarizați cu
modelele de design.
De multe ori, oamenii înțeleg doar cum se aplică anumite tehnici de proiectare a
software-ului pentru o arie restrânsa de probleme. Aceste tehnici sunt însă dificile de aplicat la
o gamă mai largă de probleme. Modelele de design oferă soluții generale, documentate într-un
format, ce nu are nevoie sa se specifice o problemă particulară.
Modelele de design au ca și origine un concept arhitectural pus in aplicare de
Christopher Alexander(1977-1979). În anul 1987, Kent Beck și Ward Cunningham au început să
experimenteze cu această idee. Rezultat-ul a constat în aplicarea modelelor de design și
prezentarea rezultatelor la conferința OOPSLA în acel an. În anii următori, Beck, Cunningham și
alții au urmat acest drum.
3
Modelele de design software au câștigat popularitate în industria software după ce
cartea „Design Patterns: Elements of Reusable Object-Oriented Software” a fost publicată în
anul 1994 de așa numiții “Gang of Four” (Grupul celor Patru - Erich Gamma, Richard Helm,
Ralph Johnson și John Vlissides ). În același an, prima conferință ce a tratat aceste modele de
design a avut loc, iar în anul următor a fost lansat “Portland Pattern Repository”, ce punea la
dispoziție toată documentația necesară folosirii modelelor de design software.
2.Creational Patterns
Modelele creationale(Creational patterns) se refera in general la instantierea claselor si
pot fi impartite in modele de creare a claselor(class-creation patterns) si modele de creare a
obiectelor(object-creational patterns) . Modelelul de creare a claselor foloseste mostenirea in
procesul de instantiere , modelelul de creare a obiectelor va delega instantierea unui alt obiect.
Modelele creationale devin importante pe masura ce sistemul evolueaza pentru a
depinde mai mult de compozitia obiectului decat de clasa mostenita .
Modelele creationale sunt :
Abstract Factory: Creeaza o instanta de mai multe familii de clase .
Builder: Separa constructia obiectului de reprezentarea lui .
Factory Method: Creeaza o instanta de mai multe clase derivate .
Object Pool: Evita achizitiile costisitoare si elibereaza resursele prin reciclarea
obiectelor care nu mai sunt utilizate.
Prototype: O instanta complet initializata pentru a fi copiata sau clonata.
Singleton: O clasa in care poate exista numai o instanta .
2.1.Abstract Factory Design Pattern
Atribute:
Asigura o interfata pentru crearea familiilor de obiecte de
acelasi tip sau obiecte dependente fara a specifica concret
clasele lor .
O ierarhie care incapsuleaza mai multe platforme posibile,
precum si constructia unui set de produse .
Noul operator e considerat daunator.
4
Fig.1 –Diagrama UML reprezentând un model de implementare pentru Abstract Factory Design Pattern
(http://sourcemaking.com/files/v2/content/patterns/Abstract_Factory_example1-2x.png)
Problema design-ului: daca o aplicatie este portabila , trebuie incapsulate dependentele
platformei. Aceste platforme pot include : sistemul de operare, baza de date , etc . De cele mai
multe ori, aceasta incapsulare nu este proiectata in avans, si o multime de declaratii de caz
(#ifdef) cu optiuni pentru toate platformele suportate in prezent incep sa se inmulteasca
extrem de repede in intregul cod.
Structura:
Modelul „Abstract Factory” defineste un model „Factory Method” pentru fiecare
produs. Fiecare model „Factory Method” incapsuleaza noul operator si clasele produselor
specifice platformelor. Fiecare platforma este modelata cu o calsa Factory derivata.
5
Fig.2 –Diagrama UML reprezentând un model de implementare pentru Abstract Factory Design Pattern
( http://sourcemaking.com/files/v2/content/patterns/Abstract_Factory-2x.png)
Exemplu de implementare Abstract Factory:
Java:
public class FactoryFmProto { static class Expression { protected String str; public Expression(String s) { str = s; } public Expression cloan() { return null; } public String toString() {
6
return str; } } static abstract class Factory { protected Expression prototype = null; public Expression makePhrase() { return prototype.cloan(); } public abstract Expression makeCompromise(); public abstract Expression makeGrade(); } static class PCFactory extends Factory { public PCFactory() { prototype = new PCPhrase(); } public Expression makeCompromise() { return new Expression("\"do it your way, any way, or no way\""); } public Expression makeGrade() { return new Expression("\"you pass, self-esteem intact\""); } } static class NotPCFactory extends Factory { public NotPCFactory() { prototype = new NotPCPhrase(); } public Expression makeCompromise() { return new Expression("\"my way, or the highway\""); } public Expression makeGrade() { return new Expression("\"take test, deal with the results\""); } } public static void main(String[] args) { Factory factory; if (args.length > 0) factory = new PCFactory(); else
7
factory = new NotPCFactory(); for (int i = 0; i < 3; i++) System.out.print(factory.makePhrase() + " "); System.out.println(); System.out.println(factory.makeCompromise()); System.out.println(factory.makeGrade()); } static class PCPhrase extends Expression { static String[] list = { "\"animal companion\"", "\"vertically challenged\"", "\"factually inaccurate\"", "\"chronologically gifted\"" }; private static int next = 0; public PCPhrase() { super(list[next]); next = (next + 1) % list.length; } public Expression cloan() { return new PCPhrase(); } } static class NotPCPhrase extends Expression { private static String[] list = { "\"pet\"", "\"short\"", "\"lie\"", "\"old\"" }; private static int next = 0; public NotPCPhrase() { super(list[next]); next = (next + 1) % list.length; } public Expression cloan() { return new NotPCPhrase(); } } }
8
Rezultatul va consta in:
"short" "lie" "old"
"my way, or the highway"
"take test, deal with the results"
"vertically challenged" "factually inaccurate" "chronologically gifted"
"do it your way, any way, or no way" "you pass, self-esteem intact"
2.2.Factory Method Design Pattern
Atribute:
Defineste o interfata pentru crearea unui obiect, dar lasa
subclasele sa decida care dintre clase va fi instantiata. Acest
model permite claselor cedarea instantierilor subclasei.
Defineste un constructor virtual .
Noul operator este considerat daunator .
Putem demosntra acest model de design astfel : producatorii de jucarii creaza jucariile
injectand plastic in mulaje de diferite forme . Tipul jucariei va fi determinat de forma mulajului.
Fig.3 –Diagrama UML reprezentând un model de implementare pentru Factory Method Design Pattern
(http://sourcemaking.com/files/v2/content/patterns/Factory_Method_example1-2x.png)
9
Structura:
Punerea in aplicare a modelului Factory Method , se suprapune in mare masura cu cea a
Abstract Factory .
Fig.4 –Diagrama UML reprezentând un model de implementare pentru Factory Method Design Pattern (http://sourcemaking.com/files/v2/content/patterns/Factory_Method-2x.png)
O definitie populara a modelului Factory Method este : o metoda statica a unei clase
care returneaza un obiect de tipul acelei clase. Spre deosebire de un constructor , obiectul ce se
returneaza ar putea fi o instant a unei subclase si un obiect existent poate fi reutilizat in loc de a
crea unul nou.
Fig.5 –Diagrama UML reprezentând un model de implementare pentru Factory Method Design Pattern
(http://sourcemaking.com/files/v2/content/patterns/Factory_Method__-2x.png)
10
Exemplu de implementare Factory Method :
Java:
public interface ImageReader {
public DecodedImage getDecodedImage();
}
public class GifReader implements ImageReader {
public GifReader( InputStream in ) {
// check that it's a gif, throw exception if it's not, then if it is
decode it.
}
public DecodedImage getDecodedImage() {
return decodedImage;
}
}
public class JpegReader implements ImageReader {
//... }
2.3.Prototype Design Pattern
Atribute:
Specifica tipul obiectelor care trebuie create cu ajutorul unei
instante prototip si creaza noi obiecte prin copierea acestui
prototip.
Racoleaza o instanta a unei clase pentru a fi utilizata pentru
creearea de noi instante.
Noul operator e considerat daunator.
11
O exemplificare a acestui model o constituie diviziunea unei celule in doua celule identice .
http://sourcemaking.com/files/v2/content/patterns/Prototype_example1-2x.png
Structura:
http://sourcemaking.com/files/v2/content/patterns/Prototype-2x.png
Exemplu de implementare Prototype :
12
Java:
public class FactoryProto {
interface Xyz { Xyz cloan(); } static class Tom implements Xyz { public Xyz cloan() { return new Tom(); } public String toString() { return "ttt"; } } static class Dick implements Xyz { public Xyz cloan() { return new Dick(); } public String toString() { return "ddd"; } } static class Harry implements Xyz { public Xyz cloan() { return new Harry(); } public String toString() { return "hhh"; } } static class Factory { private static java.util.Map prototypes = new java.util.HashMap(); static { prototypes.put( "tom", new Tom() ); prototypes.put( "dick", new Dick() ); prototypes.put( "harry", new Harry() ); } public static Xyz makeObject( String s ) { return ((Xyz)prototypes.get(s)).cloan(); } } public static void main( String[] args ) { for (int i=0; i < args.length; i++) { System.out.print( Factory.makeObject( args[i] ) + " " ); } }
}
Rezultatul va consta in:
tom dick tom harry tom ttt ddd ttt hhh ttt
13
2.4.Singleton Design Pattern
Atribute:
Se asigura ca o clasa are o singura instanta si ofera un punct global
de acces la ea .
Incapsuleaza “initializarea la prima utilizare”
Problema : Aplicatia are nevoie de o singura instanță a unui obiect. În plus, initializare lenta și
accesul global sunt necesare.
Structura:
http://sourcemaking.com/files/v2/content/patterns/singleton1-2x.png
Clasa instantei este responsabila pentru access si pentru initializarea la prima utilizare. Instanta
unica este un atribut static si privat , iar functia accesor este o metoda static si publica.
http://sourcemaking.com/files/v2/content/patterns/Singleton-2x.png
14
Exemplu de implementare Singleton:
Java:
public class Singleton {
// Private constructor prevents instantiation from other classes
private Singleton() {}
/**
* SingletonHolder is loaded on the first execution of
Singleton.getInstance()
* or the first access to SingletonHolder.INSTANCE, not before.
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
} }
3. Behavioral patterns
Un behavioral pattern explică modul în care obiectele interacționează. Acesta descrie modul in
care diferite obiecte si clase trimit mesaje unul altuia pentru a face lucrurile sa se intample si
cum etapele unei sarcini sunt împărțite între diferite obiecte.Impartirea etapelor se face prin
folosirea mostenirii.
In continuare se vor prezenta patru modele:Strategy,Template method,Iterator si Command.
Modelul Strategy încapsulează un algoritm într-un obiect. Acesta usureaza schimbarea
algoritmului pe care ifolosește un obiect.Modelul Command încapsulează o cerere într-un
obiect, astfel încât să poată fi transmisa ca parametru, stocate pe o listă istoric, sau manipulata
în alte moduri.
Template method este o definiție abstractă a unui algoritm. Aceasta definește pas cu pas algoritmul, fiecare pas invocănd fie o operație abstractă sau o operație primitiva. O subclasă detaliază algoritmul prin definirea operațiunilor abstracte.Modelul Iterator abstractizeaza modul in care accesam si parcurgem o colectie.
15
3.1 Strategy pattern 3.2.1 Introducere Strategy pattern este un model care defineste o familie de algoritmi,incapsuleaza pe fiecare si
faciliteaza interschimbarea acestor algoritmi.Este util atunci cand vrem sa alegem unul din mai
multe comportamente posibile pentru o anumita clasa.Aceasta alegere depinde de clientul
care face cererea sau de datele asupra carora se actioneaza.
Design-ul acesta este folositor si atunci cand avem nevoie sa ascundem de utilizator detaliile de
implementare ale algoritmului .
Acest pattern se bazeaza pe urmatoarele principii:
1.Obiectele au scopuri clare.
2.Implementarea acestor scopuri se realizeaza prin intermediul polimorfismului.
3.Este necesar un mod de a administra implementari diferite al aceluiasi algoritm,conceptual
vorbind.
4.Trebuie evitate tehnicile care fac posibila modificarea unei clase de catre alta clasa.
5.Trebuie evitata duplicarea codului.
3.2.2 Structura si modul de functioare
Figura preluata din “Design patterns”-Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides
16
Clasele participante :
-Strategy:este o interfata comuna pentru toti algoritmi implementati.Clasa Context foloseste
aceasta interfata pentru a apela algoritmul
definit in clasa Concrete Strategy;
-ConcreteStrategy:implementeaza algoritmul folosind interfata Strategy;
-Context:este configurata cu un obiect ConcreteStrategy;contine o referinta la un obiect
Strategy;poate sa defineasca interfata care sa permita clasei Strategy sa ii acceseze datele.
Clasele Strategy si Context interactioneaza pentru a implementa algoritmul ales.Un obiect de
tip context trimite toate datele necesare algoritmului
catre un obiect de tip Strategy atunci cand respectivul algoritm este apelat.O alta abordare este
sa fie folosita o referinta la context atunci cand este apelata obiectul Strategy.
Un context trimite mai departe catre Strategy cererile pe care le primeste de la obiectul
client,acesta lucrand exclusiv cu obiectul context.
Vom folosi acest pattern intr-un exemplu de cod preluat de pe
“java67.blogspot.ro/2014/12/strategy-pattern-in-java-with-sample.html.”
Algoritmii ce trebuie incapsulati in acest caz sunt algoritmi de ordonare:Bubble sort,Insert
sort,Quick sort si Merge sort.
public class Test {
public static void main(String args[]) throws InterruptedException {
int[] var = {1, 2, 3, 4, 5 };
Context ctx = new Context(new BubbleSort());//Initializam un obiect context care foloseste
Bubble Sort
ctx.arrange(var);
ctx = new Context(new QuickSort());//Putem schimba algoritmul folosit fara a modifica
clasa Context
ctx.arrange(var);
}
}
interface Strategy {
public void sort(int[] numbers);
}
class BubbleSort implements Strategy {
public void sort(int[] numbers) {
17
System.out.println("ordonarea vectorului folosind bubble sort");
}
}
class InsertionSort implements Strategy {
public void sort(int[] numbers) {
System.out.println("ordonarea vectorului folosind insertion sort");
}
}
class QuickSort implements Strategy {
public void sort(int[] numbers) {
System.out.println("ordonarea vectorului folosind quick sort");
}
}
class MergeSort implements Strategy {
public void sort(int[] numbers) {
System.out.println("ordonarea vectorului folosind merge sort");
}
}
class Context {
private final Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void arrange(int[] input) {
strategy.sort(input);
}
}
Output
ordonarea vectorului folosind bubble sort
ordonarea vectorului folosind quick sort
Dupa cum se poate observa schimbarea algoritmului se poate face usor ,nefiind necesara
modificarea clasei Context.
De-asemenea,daca dorim o implementare diferita a sortarii ,trebuie doar sa o incapsulam intr-o
clasa noua care sa implementeze interfata Strategy.
18
3.2.3.Aspecte ale modelului
Acest model prezinta urmatoarele avantaje:
1)Incapsularea algoritmului in clase separate permite modificarea acestuia independent de
context,facilitand schimbarea
si extinderea comportamentului clasei Context.
2)Acest pattern reprezinta o alternativa la structurile conditionale folosite pentru selectarea
comportamentului
dorit.Acest structuri sunt folosite atunci cand posibilele comportamente ale unui clase sunt
definite chiar in clasa respectiva.
3)Ofera o serie de implementari ale aceluiasi comportament,clientul putand sa aleaga intre mai
multe strategii cu diferite performante.
Acest model prezinta urmatoarele dezavantaje:
1)Clientii trebuie sa stie cate strategii exista si cum difera acestea.
2)Clasele ConcreteStrategy folosesc aceeasi interfata,indiferent de gradul de complexitate al
algoritmilor pe care ii incapsuleaza.
Din acest motiv este posibil sa nu fie folosite toate informatiile primite prin interfata.Asta
inseamna ca vor fi exista parametrii
care sunt creati si initializati fara a fi folositi.
3)Va exista un numar mare de obiecte .
3.3 Template method
3.3.1 Introducere
Template method este un pattern folosit pentru a implementa partile constante ale
unui algoritm,permitand subclaselor sa stabileasca comportamentul care variaza.
Acest pattern ar trebui folosit atunci cand vrem sa grupam comportamentul comun
al mai multor subclase intr-o singura clasa,evitand cod duplicat.
19
3.3.2 Structura si modul de functionare
Figura preluata din “Design patterns”-Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides
Clasele participante
-AbstractClass:defineste operatii primitive pe care subclasele le definesc pentru a implementa
pasii unui algoritm defineste o metoda template care descrie scheletul unui algoritm
-ConcreteClass:implementeaza operatiile primitive care realizeaza pasii algoritmului specifici
subclasei
ConcreteClass se bazeaza pe AbstractClass sa implementeze pasii invarianti ai algoritmului.
Exemplu de folosire al pattern-ului:http://www.journaldev.com/1763/template-method-
design-pattern-in-java
public abstract class HouseTemplate {
// metoda template
public final void buildHouse(){
buildFoundation();
buildPillars();
buildWalls();
buildWindows();
System.out.println("House is built.");
}
20
private final void buildWindows() {
System.out.println("Building Glass Windows");
}
// operatiile primitive ce trebuie implementate de catre subclase
public abstract void buildWalls();
public abstract void buildPillars();
private final void buildFoundation() {
System.out.println("Building foundation with cement,iron rods and sand");
}
}
public class WoodenHouse extends HouseTemplate {
//subclasa modifica metodele care au o implementare necorespunzatoare
public void buildWalls() {
System.out.println("Building Wooden Walls");
}
public void buildPillars() {
System.out.println("Building Pillars with Wood coating");
}
}
public class GlassHouse extends HouseTemplate {
public void buildWalls() {
System.out.println("Building Glass Walls");
}
public void buildPillars() {
System.out.println("Building Pillars with glass coating");
}
}
3.3.3 Aspecte ale modelului
Metodele care sunt comune pentru toate subclasele raman in metoda template neschimbate,in
timp ce subclasele pot sa modifice metodele fara a se influenta unu pe alta.
Metodele abstracte trebuie implementate de catre subclase(de exemplu buildWalls si
buildPillars).
21
In metoda Template pot sa existe si asa numitele metode hook a caror modificare ramane la
alegerea clientului.
Un obiectiv important în proiectarea metodelor template este de a reduce numărul de
operațiuni primitive pe care o subclasa trebuie să suprascrie pentru a concretiza algoritmul. Cu
cat mai multe operațiuni au nevoie de suprascriere, cu atat devin lucrurile mai plictisitoare
pentru clienti.
3.4 Iterator
3.4.1.Introducere
Acest design pattern ne ofera urmatoarele lucruri:
-un mod uniform de accesa diferite colectii de Objects fara a dezvalui reprezentarea interna a
acestora,
-moduri diferite de parcurgere a acestor colectii,
-parcurgeri multiple ale colectiilor
3.4.1.Structura si modul de functionare
Figura preluata din “Design patterns”-Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides
Clase participante:
1)Iterator,
2)ConcreteIterator,
22
3)Aggregate ,
4)ConcreteAggregate.
Clasa Iterator defineste o interfata pentru accesarea si parcurgerea elementelor.
Metodele definite in aceasta clasa sunt folosite in acest scop:
First()-ne spune care este primul element
Next()- ne spune care este urmatorul element din colectie .
ConcreteIterator implementeaza interfata Iterator si tine cont de pozitia curenta in
parcurgerea colectiilor,putand sa determine obiectul urmator care trebuie parcurs
Aggregate defineste o interfata pentru realizarea unui obiect Iterator(metoda CreateIterator()
este folosita in acest sens)
ConcreteAggregate returneaza o instanta propriu-zisa de ConcreteIterator
O problema fundamentala care trebuie stabilita este cine controleaza iteratia:
Iteratorul sau clientul care foloseste iteratorul.
În cazul în care clientul controlează repetarea, iteratorul este numit un iterator extern, iar
atunci când iterator controlează, iterator este un iterator intern.
Iteratorii externi sunt mai flexibili decât iteratorii interne. Este ușor să se verifice daca două
colecții sunt egale cu un iterator extern, de exemplu, dar este practic imposibil cu iteratori
interni.
Clasa Iterator nu este singurul loc în care algoritmul de parcurgere poate fi definit. Clasa
Aggregate poate defini algoritmul de traversare și poate folosi iteratorul pentru a stoca doar
starea iterației. Acest tip de iterator se numeste cursor, deoarece indică numai poziția curentă.
Un client va invoca operația Nextl pe colectie cu cursorul ca un argument, iar operațiunea
viitoare va schimba starea cursorului.
În cazul în care iterator este responsabil pentru algoritmul de traversare, atunci poate folosi
diferiti algoritmi de iterație pe aceeasi colectie, și poate , de asemenea, să reutilizeze același
algoritm pe diferite colectii. Pe de altă parte, algoritmul de parcurgere ar putea avea nevoie sa
acceseze variabilele private ale agregatului. Dacă este așa, nu se respecta incapsularea colectiei.
Urmeaza un exemplu de cod Java in care este folosit acest pattern():
interface IIterator { public boolean hasNext(); public Object next(); }
23
interface IContainer { public IIterator createIterator(); }
class BooksCollection implements IContainer { private String m_titles[] = {"Design Patterns","1","2","3","4"}; public IIterator createIterator() { BookIterator result = new BookIterator(); return result; } private class BookIterator implements IIterator { private int m_position; public boolean hasNext() { if (m_position < m_titles.length) return true; else return false; } public Object next() { if (this.hasNext()) return m_titles[m_position++]; else return null; } } }
Avem urmatoarele clase in acest exemplu:
- IIterator – Aceasta interfata reprezinta clasa AbstractIterator; - BookIterator – Implementarea clasei Iterator - IContainer – Interfata care defineste colectia - BooksCollection – Implementarea colectiilor
24
3.4.3Aspecte ale modelului
O problema fundamentala care trebuie stabilita este cine controleaza iteratia:
Iteratorul sau clientul care foloseste iteratorul.
În cazul în care clientul controlează repetarea, iteratorul este numit un iterator extern, iar
atunci când iterator controlează, iterator este un iterator intern.
Iteratorii externi sunt mai flexibili decât iteratorii interne. Este ușor să se verifice daca două
colecții sunt egale cu un iterator extern, de exemplu, dar este practic imposibil cu iteratori
interni.
Clasa Iterator nu este singurul loc în care algoritmul de parcurgere poate fi definit. Clasa
Aggregate poate defini algoritmul de traversare și poate folosi iteratorul pentru a stoca doar
starea iterației. Acest tip de iterator se numeste cursor, deoarece indică numai poziția curentă.
Un client va invoca operația Nextl pe colectie cu cursorul ca un argument, iar operațiunea
viitoare va schimba starea cursorului.
În cazul în care iterator este responsabil pentru algoritmul de traversare, atunci poate folosi
diferiti algoritmi de iterație pe aceeasi colectie, și poate , de asemenea, să reutilizeze același
algoritm pe diferite colectii. Pe de altă parte, algoritmul de parcurgere ar putea avea nevoie sa
acceseze variabilele private ale agregatului. Dacă este așa, nu se respecta incapsularea colectiei.
3.5 Command pattern
3.5.1.Introducere
Command pattern-ul este un behavioral pattern in care un obiect este folosit pentru a
reprezenta si incapsula toata informatia necesara pentru a apela ulterior o metoda.
Uneori este necesar pentru a emite cereri de obiecte, fără să se știe nimic despre operațiunea
ce se solicită sau despre receptorul cererii. De exemplu, seturile de instrumente de UI(user
interface) includ obiecte precum butoane și meniuri care efectuează o cerere ca raspuns la
inputul utilizatorului..
Setul de instrumente insa nu poate pune în aplicare cererea în mod explicit în buton sau meniu,
pentru că numai aplicațiile care utilizează setul de instrumente știu ce trebuie făcut pe care
obiect.
Command pattern-ul le permite seturilor de instrumente sa faca cereri de obiecte
nespecificate prin transformarea cererii insesi intr-un obiect. Acest obiect poate fi stocat și
trimis ca orice alte obiecte. Cheia acestui model este o clasă abstractă de comandă, care
declară o interfață pentru operațiuni de executare. În cea mai simplă formă această interfață
include operatie abstracta Execute. Subclasele derivate din Command specifica o pereche
receptor-acțiune prin stocarea receptorului ca o variabilă instanță și prin punerea în aplicare a
25
metodei Execute pentru a invoca cererea. Receptorul are cunoștințele necesare pentru a
îndeplini cererea.
3.5.2.Structura si modul de functionare
Figura preluata din “Design patterns”-Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides
Clasele participante:
1)Command- declara o interfata pentru executarea unei operatii,
2)ConcreteCommand- defineste o legatura intre un obiect receptor si o actiune,
3)Client- creeaza un obiect ConcreteCommand si stabileste receptorul lui,
4)Invoker- solicita comanda pentru a indeplini cererea,
5)Receiver- stie cum sa realizeze operatiile asociate cu indeplinirea unei cereri.
Urmeaza un exemplu de program care foloseste command pattern-ul preluat de pe
http://java.dzone.com/articles/design-patterns-command
1.Mai intai cream interfata de comanda
//Command public interface Command { public void execute(); }
2.Cream 2 ConcreteCommands:una pentru a aprinde lumina si una a pentru a stinge lumina
public class LightOnCommand implementsCommand { Light light;
26
public LightOnCommand(Light light) { this.light = light; } public void execute() { light.switchOn(); } } public class LightOffCommand implementsCommand { Light light; public LightOffCommand(Light light) { this.light = light; } public void execute() { light.switchOff(); } } 3.Light este clasa Receiver
public class Light { private boolean on; public void switchOn() { on = true; } public void switchOff() { on = false; } }
27
4.Clasa Invoker este reprezentata de RemoteControl
public class RemoteControl { private Command command; public void setCommand(Command command) { this.command = command; } public void pressButton() { command.execute(); } }
5.Cream un Client pentru a folosi clasa Invoker
public class Client { public static void main(String[] args) { RemoteControl control = new RemoteControl(); Light light = new Light(); Command lightsOn = new LightsOnCommand(light); Command lightsOff = new LightsOffCommand(light); control.setCommand(lightsOn); control.pressButton(); control.setCommand(lightsOff); control.pressButton(); } }
3.5.3 Aspecte de implementare O comandă poate avea o gamă largă de abilități. Pe de o parte ea poate defini doar o legatura
între un receptor și acțiunile care realizează cererea,iar pe de alta parte poate implementa totul
.Cea din urmă varianta este utilă atunci când dorim să definim comenzi care sunt independente
de clase existente, atunci când nu există receptor adecvat, sau atunci când o comandă cunoaște
receptorul implicit.
28
Comenzile pot realiza operatii de tip Undo și Redo dacă acestea oferă o modalitate de a inversa
executarea lor (de exemplu, o operatie Unexecute sau Undo).
Pentru a sprijini un nivel de undo, o aplicatie are nevoie sa stocheze doar comanda care a fost
executata ultima. Pentru niveluri multiple de Undo si Redo, aplicația are nevoie de o listă istoric
de comenzi care au fost executate, unde durata maximă a listei determină numărul de niveluri
undo / redo.Lista istorie stochează secvențe de comenzi care au fost executate. Traversează
înapoi prin listă și comenzile de executare inversa anulează efectul lor; traversează înaintează și
comenzile de executare le reexecuta
Acest mod de lucru este util atunci cand vrem sa structuram un sistem în jurul operațiunilor de
nivel înalt construite pe operatiuni primitive. O astfel de structură este comună sistemelor de
informații care acceptă tranzacții. O tranzacție încapsulează un set de modificări de date.
Command pattern-ul oferă o modalitate de a modela tranzacții. Comenzile au o interfață
comună, permitand invocarea tuturor tranzactiilor în același mod.Este, de asemenea, ușor de a
extinde sistemul cu noi tranzactii.
4.Structural Patterns
Principala sarcina a modelelor structurale (Structural patterns) consta in modul în care
clasele și obiectele sunt compuse pentru a forma structuri mai mari. Modelele de clasa
structurale folosesc conceptul de moștenire pentru a compune interfețe sau diverse
implementări. Ca un exemplu simplu,se ia în considerare, pentru conceptul de moștenire, cât
de mult se amestecă două sau mai multe clase întruna singura. Rezultatul va fi o clasă ce
combină proprietățile claselor sale mamă. Acest model este deosebit de util pentru construii
biblioteci de clase dezvoltate independent, dar care sa lucreze împreună. Un alt exemplu este
forma de clasă specifica pattern-ului Adapter. În general, un adaptor are rolul de a face ca o
interfață („adaptee”) sa fie conforma cu alta, oferind astfel o abstracție uniformă de interfețe
diferite. O clasa adaptor realizează acest lucru prin mostenirea privata dintr-o clasă adaptee.
Modelele structurale sunt:
Adapter – adaptează interfețele diferite ale claselor obiect;
Bridge – separa interfața unui obiect de implementarea acestuia;
Composite – creează o structura de tip arbore de obiecte simple si compuse;
Decorator – adaugă proprietăți obiectelor in mod dinamic;
Facade – creează o singura clasa ce poate reprezenta un întreg subsistem;
Flyweight – o instanțiere folosita pentru a realiza o partajare eficienta;
Private Class Data – controlează accesul la atributele clasei obiect;
Proxy – creează un obiect cu referință la un alt obiect;
29
4.1.Adapter Design Pattern
Atribute:
Converteste interfata unei clase in interfata pe care clientul o asteapta sa o
primeasca. Astfel, clasele ce nu puteau functiona impreuna din cauza
incompabilitati dintre interfete, o pot face acum prin prisma Adaptorului.
Impacheteaza o clasa existenta intr-o interfata noua.
Reprezinta o punte a independetii privind folosirea unei componente vechi
intr-un sistem nou.
Problema aparitiei design-ului: atunci cand se doreste reutilizarea unei componente ce nu mai
este actuala, dar din punct de vedere tehnic ofera intreaga functionalitate necesara si nu este
compatibila cu filozofia si arhitectura sistemului in curs de dezvoltare.
http://sourcemaking.com/files/v2/content/patterns/Adapter_example1-2x.png
Adaptor presupune crearea unui abstracții intermediare care sa traduca, sau sa mapeze,
componenta veche la noul sistem. Clientii apeleaza metode asupra obiectului adaptor pe care le
redirecționează în apeluri la componenta de moștenire. Această strategie poate fi pusa în
aplicare fie cu metoda moștenirii sau cea de agregare.
30
Adaptorul functioneaza ca un înveliș sau modificator al unei clase existente. Acesta oferă un
punct de vedere diferit sau tradus din clasa respectivă.
Structura: mai jos, se poate observa componenta display() a mostenirii Rectangle, ce asteapta
sa primeasca parametrii x,y,w si h, dar clientul vrea sa transmita x1 si y1 pentru coltul stanga
sus si x2 si y2 pentru coltul dreapta joc. Aceasta lipsa de coerenta dintre interfete poate fi
reconciliata prin adaugarea unui nivel suplimentare de indirectare, si anume un obiect adaptor.
http://sourcemaking.com/files/v2/content/patterns/Adapter_1-2x.png
Exemplu implementare pattern: (cod preluat de pe http://sourcemaking.com/design_patterns/adapter/java/1)
Inainte de implementare
Deoarece interfetele dintre obiectele Line si Rectangle sunt incompatibile, utilizatorul va tebuii
sa recupereze tipul fiecarei forme si sa livreze manual argumentele corecte.
Java: class LegacyLine
{
public void draw(int x1, int y1, int x2, int y2)
{
System.out.println("line from (" + x1 + ',' + y1 + ") to (" + x2 + ','
+ y2 + ')');
}
}
31
class LegacyRectangle
{
public void draw(int x, int y, int w, int h)
{
System.out.println("rectangle at (" + x + ',' + y + ") with width " + w
+ " and height " + h);
}
}
public class AdapterDemo
{
public static void main(String[] args)
{
Object[] shapes =
{
new LegacyLine(), new LegacyRectangle()
};
// punctele de origine si de sfarsit ale editorului grafic
int x1 = 10, y1 = 20;
int x2 = 30, y2 = 60;
for (int i = 0; i < shapes.length; ++i)
if (shapes[i].getClass().getName().equals("LegacyLine"))
((LegacyLine)shapes[i]).draw(x1, y1, x2, y2);
else if (shapes[i].getClass().getName().equals("LegacyRectangle"))
((LegacyRectangle)shapes[i]).draw(Math.min(x1, x2), Math.min(y1, y2)
, Math.abs(x2 - x1), Math.abs(y2 - y1));
} }
Rezultatul va consta in: ”line from (10,20) to (30,60)”
“rectangle at (10,20) with width 20 and height 40”
Dupa de implementare
Nivelul suplimentar de indirectare al Adaptorului are grija sa mapeze o interfata comuna la
interfata specifica mostenita.
Java: class LegacyLine
{
public void draw(int x1, int y1, int x2, int y2)
{
System.out.println("line from (" + x1 + ',' + y1 + ") to (" + x2 + ','
+ y2 + ')');
}
}
class LegacyRectangle
{
32
public void draw(int x, int y, int w, int h)
{
System.out.println("rectangle at (" + x + ',' + y + ") with width " + w
+ " and height " + h);
}
}
interface Shape
{
void draw(int x1, int y1, int x2, int y2);
}
class Line implements Shape
{
private LegacyLine adaptee = new LegacyLine();
public void draw(int x1, int y1, int x2, int y2)
{
adaptee.draw(x1, y1, x2, y2);
}
}
class Rectangle implements Shape
{
private LegacyRectangle adaptee = new LegacyRectangle();
public void draw(int x1, int y1, int x2, int y2)
{
adaptee.draw(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1),
Math.abs(y2 - y1));
}
}
public class AdapterDemo
{
public static void main(String[] args)
{
Shape[] shapes =
{
new Line(), new Rectangle()
};
// punctele de origine si de sfarsit ale editorului grafic
int x1 = 10, y1 = 20;
int x2 = 30, y2 = 60;
for (int i = 0; i < shapes.length; ++i)
shapes[i].draw(x1, y1, x2, y2);
} }
Rezultatul va consta in: ”line from (10,20) to (30,60)”
“rectangle at (10,20) with width 20 and height 40”
33
4.2.Bridge Design Pattern
Atribute:
Separa o abstractie de implementarea sa, astfel incat cele doua pot varia
independent una fata de alta.
Publica interfata intr-o ierarhie mostenita, si trimite partea de
implementarea in propia ierarhie mostenire
Pattern-ul Bridge este un model structural de design care separă elementele abstracte ale unei
clase de la implementarea sa tehnică. Acest lucru oferă o implementare curata a obiectelor din
lumea reală și permite ca implementarea detaliilor poată fi schimbata cu ușurință.
Pattern-ul Bridge este un model foarte valoros, deoarece permite separarea elementelor
abstracte din clasa de detaliile necesare implementării. Acest model poate fi folosit, în cazul în
care clasa sufera modificari din cauza alterarii codului sursa, avand cunoștințe minime despre
program.
Problema aparitiei design-ului: Conceptul de "Hardening of the software arteries" (rigidizarea
arterelor software) a avut loc prin utilizarea unei subclase detinuta de o clasa abstracta de baza
pentru a oferii alternative in procesul de implementare. Acest lucru blocheaza timpul de
compilare dintre interfata si implementare. Abstractizarea si implementarea nu pot fi extinse si
compuse independent una fata de cealalta.
Structura: clientul nu vrea sa trateze detaliile legate de platforma. Pattern-ul Bridge
incapsuleaza aceasta complexitate in spatele unei abstractizari „wrapper”. Pattern-ul identifica
si deconecteaza interfata abstractizarii de implementarea abstractizata.
http://sourcemaking.com/files/v2/content/patterns/Bridge___-2x.png
34
De exemplu un comutator de uz casnic pentru control luminii, ventilatiei, etc. este un astfel de
Bridge. Scopul comutatorul este de a pornii sau a oprii un dispozitiv. Comutatorul real poate fi
implementat simplu, comutator cu două poziții, sau un comutator variabil.
http://sourcemaking.com/files/v2/content/patterns/Bridge_example-2x.png
Exemplu implementare pattern: (cod preluat de pe http://www.gofpatterns.com/structural-design-
patterns/structural-patterns/bridgePattern-code.php)
Codul JAVA de mai jos pune in aplicare pattern-ul folosind ca exemplu o telecomanda TV.
Mai intai implementarea interfetei TV:
package com.java.structural.bridge; //Implementarea public interface TV{ public void on(); public void off(); public void tuneChannel(int channel); }
Sunt create doua implementari specifice: una pentru marca Sony si una pentru marca Samsung:
package com.java.structural.bridge; //Implementarea fizica
public class Sony implements TV{ public void on(){ //comanda pornit specifica Sony
} public void off(){
// comanda oprit specifica Sony }
public void tuneChannel(int channel){ // comanda “schimbare post” specifica Sony } }
35
package com.java.structural.bridge; //Implementarea fizica
public class Samsung implements TV{ public void on(){ //comanda pornit specifica Samsung
} public void off(){
// comanda oprit specifica Samsung }
public void tuneChannel(int channel){ // comanda “schimbare post” specifica Samsung } }
Aceste clase se confrunta cu implementări specifice fiecarui producator de televizor. In
continuare este creata abstractia „telecomanda” pentru a controla TV-ul.
package com.java.structural.bridge; //Abstractia
public abstract class RemoteControl{ private TV implementor; public void on(){ implementor.on(); } public void off(){ implementor.off(); } public void setChannel(int channel){ implementor.tuneChannel(channel); } }
In timp ce “telecomanda” contine referinte despre TV poate delega metode de apelare prin intermediul interfetei. Pentru implementarea butoanelor de “+/-“ pentru navigarea intre canalele de televiziune, abstractia “telecomanda” trebuie extinsa pentru a contine acest concept: package com.java.structural.bridge; //Abstractie rafinata public class ConcreteRemote extends RemoteControl{ private int currentChannel; public void nextChannel(){ currentChannel++; setChannel(currentChannel); } public void prevChannel(){ currentChannel--; setChannel(currentChannel); } }
36
Un dezavantaj major al acestui pattern, cu toate ca ofera o flexibilitate crescuta, este acela ca
va creste complexitatea programului.
4.3.Composite Design Pattern
Atribute:
Descompune obiectele in structuri de arbori pentru a reprezenta o ierarhie
intreaga.
Lasa clientii sa trateze obiectele individual si compunerea obiectelor
uniforma.
Compozitie recursiva.
Directoare ce contin intrari, acele intrari pot fi la randul lor alte directoare.
Problema aparitiei design-ului: aplicatiile au nevoie sa manipuleze ierarhic intreaga colectie de
obiecte „primitive” si „composite”. Procesarea celor „primitive” difera de cea a obiectelor
„composite”. Era nevoie ca inainte de inceperea procesarii obiectelor, acestea sa fie
„interogate” aflandu-se tipul acestor si ce metoda trebuie aplicata pentru satisfacerea
procesului. Acest lucru nu a fost dorit.
Structura: cosnsta in faptul ca o compozitie ce contine componente, aceste componente pot fi
la randul lor alte compozitii.
http://sourcemaking.com/files/v2/content/patterns/Composite-2x.png
37
Un meniu ce contine intem-uri, fiecare din aceste item-uri poate fi la randul lui un alt meniu.
O expresie aritmetica ce presupune un operand, o operatie (+ - / * ) si un alt operand, poate
alcatuii un alt exemplu. Operand-ul poate fi un numar sau o alta expresie aritmetica. Asadar 2
+3 si (2+3)+(4*6), ambele sunt expresii valide.
http://sourcemaking.com/files/v2/content/patterns/Composite_example1-2x.png
Exemplu implementare pattern: Un atribut static în clasa de bază Entity poate fi manipulat prin
fiecare obiect Box pentru a controla procesul de indentare.
(cod preluat de pe http://sourcemaking.com/design_patterns/composite/java/3)
Java: import java.util.List;
import java.util.ArrayList;
abstract class Entity {
protected static StringBuffer indent = new StringBuffer();
public abstract void traverse();
}
class Product extends Entity {
private int value;
public Product( int val ) { value = val; }
public void traverse() {
System.out.println( indent.toString() + value );
} }
38
class Box extends Entity {
private List children = new ArrayList();
private int value;
public Box( int val ) { value = val; }
public void add( Entity c ) { children.add( c ); }
public void traverse() {
System.out.println( indent.toString() + value );
indent.append( " " );
for (int i=0; i < children.size(); i++)
((Entity)children.get(i)).traverse();
indent.setLength( indent.length() - 3 );
} }
public class CompositeLevels {
public static void main( String[] args ) {
Box root = initialize();
root.traverse();
}
private static Box initialize() {
Box[] nodes = new Box[7];
nodes[1] = new Box( 1 );
int[] s = { 1, 4, 7 };
for (int i=0; i < 3; i++) {
nodes[2] = new Box( 21+i );
nodes[1].add( nodes[2] );
int lev = 3;
for (int j=0; j < 4; j++) {
nodes[lev-1].add( new Product( lev*10 + s[i] ) );
nodes[lev] = new Box( lev*10 + s[i]+1 );
nodes[lev-1].add( nodes[lev] );
nodes[lev-1].add( new Product( lev*10 + s[i]+2 ) );
lev++;
} }
return nodes[1]; } }
39
Rezultatul va consta in:
1
21
31
32
41
42
51
52
61
62
63
53
43
33
22
34
35
44
45
54
55
64
65
66
56
46
36
23
37
38
47
48
57
58
67
68
69
59
49 39
40
4.4.Decorator Design Pattern
Atribute:
Ataseaza propietati aditionale asupra unui obiect in mod dinamic.
Infrumuseteaza nucleul unui obiect la cererea unui client prin wrapping
recursiv.
Problema aparitiei design-ului: cand se vrea adaugarea unui comportament sau unei stari
asupra unui obiect individual in timpul executiei. Metoda mostenirii nu este posibila, deoarece
aceasta este statica si se aplica pentru intreaga clasa.
Solutia acestei probleme presupune incapsularea obiectului original in initeriorul unei interfete
abstracte wrapper. Atat obiectul decorator cat si obiectul nucleu moștenesc de aceasta
interfața abstracta. Interfata foloseste compunerea recursiva pentru a permite adaugarea unui
numar nelimitat de layere de tip decorator asupra fiecarui nucleu de obiect.
Dupa aplicarea pattern-ului identitatea nucleului a fost ascunsa inautr-ul obiectului decorator.
Incercarea accesarii nucleului obiectului in mod direct reprezinta acum, noua problema.
Structura: clientul este intotdeauna interesat in CoreFunctionality.doThis(). Clientul poate, sau
poate nu, sa reprezinte interes in OptionalOne.doThis() si in OptionalTwo.doThis(). Fiecare
dintre aceste clase trimite catre clasa de baza Decorator si aceasta clasa trimite la randul ei
catre obiectul “wrappee”.
http://sourcemaking.com/files/v2/content/patterns/Decorator__1-2x.png
41
Exemplu implementare pattern:
(cod preluat de pe http://sourcemaking.com/design_patterns/decorator/java/3)
Java:
// 1. "lowest common denominator"
interface Widget {
void draw();
}
// 3. "Core" class with "is a" relationship
class TextField implements Widget {
private int width, height;
public TextField( int w, int h ) {
width = w;
height = h;
}
public void draw() {
System.out.println( "TextField: " + width + ", " + height );
}
}
// 2. Second level base class with "isa" relationship
abstract class Decorator implements Widget {
private Widget wid; // 4. "has a" relationship
public Decorator( Widget w ) {
wid = w;
}
// 5. Delegation
public void draw() {
wid.draw();
}
}
// 6. Optional embellishment
class BorderDecorator extends Decorator {
public BorderDecorator( Widget w ) {
super( w );
}
public void draw() {
42
super.draw(); // 7. Delegate to base class and add extra stuff
System.out.println(" BorderDecorator");
}
}
// 6. Optional embellishment
class ScrollDecorator extends Decorator {
public ScrollDecorator( Widget w ) {
super( w );
}
public void draw() {
super.draw(); // 7. Delegate to base class and add extra stuff
System.out.println( " ScrollDecorator" );
}
}
public class DecoratorDemo {
public static void main( String[] args ) {
// 8. Client has the responsibility to compose desired configurations
Widget aWidget = new BorderDecorator(
new BorderDecorator(
new ScrollDecorator(
new TextField( 80, 24 ))));
aWidget.draw();
} }
43
5.Bibliografie:
1.” Design Patterns_ Elements of Reusable Object-Oriented Software” Erich Gamma, Richard
Helm, Ralph Johnson, John M. Vlissides
2. “Head First Design Patterns “Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra
3.www. java67.blogspot.ro
4. www.journaldev.com
5. www.oodesign.com/iterator-pattern.html
6.www. java.dzone.com/
7. http://sourcemaking.com/