Tecniche di Progetto (a.k.a. Design Patterns). Progettazione Object-Oriented Passi della prassi...
-
Upload
donatella-micheli -
Category
Documents
-
view
217 -
download
0
Transcript of Tecniche di Progetto (a.k.a. Design Patterns). Progettazione Object-Oriented Passi della prassi...
Tecniche di Progetto(a.k.a. Design Patterns)
Progettazione Object-Oriented
Passi della prassi
• Definizione delle classi
• Determinazione delle responsabilità di ciascuna classe
• Descrizione delle relazioni tra classi
Scoperta delle classi
• Classi rappresentano concetti ed entità entità concrete: conti bancari, forme geometriche, … concetti astratti: streams …
Relazioni tra classi
• Ereditarietà (sottotipo)
• Aggregazione
• Dipendenza
Ereditarietà
• Relazione is-a
• Stabilita tra una classe generale ed una sua specializzazione ogni savings accoung è un bank account ogni cerchio è una ellisse …
Continued…
Aggregazione
• Relazione has-a
• Oggetti di una classe contengono riferimenti ad oggetti di un altra classe
• Ogni automobile ha una ruota (in realtà ne ha quattro … )
• Da non confondere con ereditarietà
is-a vs has-a
class Car extends Vehicle{ . . . private Tire[] tires;}
Dipendenza
• Una relazione di uso
• Esempio: molte delle applicazioni che abbiamo visto dipendono dalla classe Scanner
• Aggregazione è una forma più forte di dipendenza
Progettazione Object-Oriented
Passi della prassi
• Definizione delle classi
• Determinazione delle responsabilità di ciascuna classe
• Descrizione delle relazioni tra classi
Obiettivi
• Garantire astrazione
• Massimizzare riuso
• Progetto componenti astratte pattern: iterator
• Progetto di componenti riusabili/polimorfe tecniche comuni di refactoring e generalizzazione patterns: template, strategy, visitor, decorator …
Progetto di componenti astratte
TIPI DI DATO: Abstract vs Concrete
• Lista astratta Una sequenza ordinata di elementi di un certo tipo
che si possono enumerare con un iteratore
• Lista concreta Una sequenza di nodi che puntano ad oggetti di un
certo tipo e sono collegati mendiante riferimenti
Una lista astratta
Una lista concreta
Un ListIterator astratto
Un ListIterator concreto
Pattern – Iterator
ConcreteCollection<E>
iterator(). . .
AbstractCollection<E>
iterator(). . .
return new ConcreteIterator<E>(this)
Client Iterator<E>
next()hasNext()
ConcreteIterator<E>
Progetto di componenti polimorfe
• Polimorfo ~ Riutilizzabile
• Generics forniscono un meccanismo diretto per ottenere componenti riutilizzabili
• Ereditarietà e aggregazione possono essere altrettanto efficaci alcune tecniche standard codificate in design patterns
Refactoring
• Identificare segmenti di codice ricorrenti che realizzano la medesima logica
• Definire una nuova componente generica che realizza quella logica in modo univoco
• Ristrutturare il codice così da sostituire le ripetizioni di codice con riferimenti alla componente generica
Refactoring di una classe
class Computation // Prima del refactoring
{ void method1(...) { ... step1(); step2(); step3(); ... }
void method2(...) { ... step1(); step2(); step3(); ... }}
Continua…
Refactoring di una classeclass Computation // Dopo del refactoring { void private computeAll() { step1(); step2(); step3(); }
void method1(...) { ... computeAll(); ... }
void method2(...) { ... computeAll(); ... }}
Refactoring di più classi
class ComputationA { void method1(...) {...; step1(); step2(); step3(); ...}}
class ComputationB { void method2(...) {...; step1(); step2(); step3(); ...}}
• Codice duplicato su diverse classi
Continua…
Refactoring di più classi
• Refactoring via ereditarietà
class Common { void commonSteps() { step1(); step2(); step3(); }}
class ComputationA extends Common { void method1(...) { ... ; commonSteps(); ... }}
class ComputationB extends Commom { void method2(...) { ... ; commonSteps(); ... }}
Continua…
• Refactoring via aggregazione e delegation
Refactoring di più classi
class Helper { void commonSteps() { step1(); step2(); step3(); }}
class ComputationA{ Helper help; void method1(...) { ... ; help.commonSteps(); ... }}
class ComputationB{ Helper help; void method2(...) { ... ; help.commonSteps(); ... }}
Refactoring – UML
ComputationA
method1()method2()
ComputationB
method1()method2()
Continua…
• Refactoring via inheritance
Refactoring – UML
ComputationA
method1()method2()
ComputationB
method1()method2()
Common
commonSteps()
Continua…
• Refactoring via aggregazione e delegation
Refactoring – UML
ComputationA
method1()method2()
ComputationB
method1()method2()
Helper
commonSteps()
helperhelper
Una situazione più complessa
class ContextA { void method(...) { <codice comune 1>; stepA(); <codice comune 2>; }}
class ContextB { void method(...) { <codice comune 1>; stepB(); <codice comune 2>; }}
Generalizziamo la soluzione?
class Common { commonCode1() { <codice comune 1> } commonCode2() { <codice comune 2> }}
class ContextA extends Common { void method(...) {commonCode1(); stepA(); commonCode2();}}
class ContextB { void method(...) {commonCode1(); stepB(); commonCode2();}}
Continua…
Generalizziamo la soluzione?
• Uhm …
• se i due frammenti di codice comune sono strettamente dipendenti tra loro possibile che separarli generi errori rompe il flusso del controllo naturale peggiora la leggibilità del codice
• se i due frammenti sono parte dello stesso comando composto (ad esempio, un ciclo) non realizzabile
Esempio: due Plotters
• Plotter per la funzione sin(x)
class PlotSin { . . .
protected void plotFunction(Graphics g) {
for (int px = 0; px < d.width; px++) {double x = (double)(px - xorigin) / (double)xratio;double y = Math.sin(x);int py = yorigin - (int) (y * yratio);g.fillOval(px - 1, py - 1, 3, 3);
}}
Continua…
Esempio: due Plotters
• Plotter per la funzione cos(x)
class PlotCos { . . .
protected void plotFunction(Graphics g) {
for (int px = 0; px < d.width; px++) {double x = (double)(px - xorigin) / (double)xratio;double y = Math.cos(x);int py = yorigin - (int) (y * yratio);g.fillOval(px - 1, py - 1, 3, 3);
}}
Una situazione più complessa
class ContextA { void method(...) { <codice comune 1>; stepA(); <codice comune 2>; }}
class ContextB { void method(...) { <codice comune 1>; stepB(); <codice comune 1>; }}
Refactoring con il pattern Template
abstract class Common{ void methodoTemplate (...) { <codice comune 1>; metodoHook(...); <codice comune 2> } abstract void metodoHook();}
class ContextA extends Common { void metodoHook(...) { stepA(); }}
class ContextB extends Common { void metodoHook(...) { stepB(); }}
Pattern Template
classe Concreta
methodHook1()methodHook2()
classe Generica
metodoTemplate()metodoHook1();metodoHook2();
. . . metodoHook1();. . . methdoHook2(). . .
Esempio: progetto di animazioni
• Una applicazione del pattern template
• Fattorizza la logica di animazione in una classe astratta
• Lascia la definizione dell’immagine da animare alle sottoclassi concrete
• Vediamo la classe astratta Animator due classi concrete
•BouncingBall, DigitalClock
Animatorpublic abstract class Animator
extends JComponent implements ActionListener { private int delay; private Timer T;
protected Animator(int delay) { this.delay = delay; setPreferredSize(new Dimension(getWidth(),getHeight())); T = new Timer(delay, this); } // schema di animazione: guidata dagli eventi del timer public void animate(){ T.start(); } public void actionPerformed(ActionEvent event){ repaint(); }
// metodo Hook public abstract void paintComponent(Graphics g);}
Domanda
• Perché la classe Animator è abstract ?
• Quale è il metodo template?
• Quale è il metodo hook?
Risposta
• E’ abstract perché non definisce il metodo paintComponent(), che gioca il ruolo di metodo hook in questa implementazione
• Il metodo template è actionPerformed() (che invoca paintComponent() via repaint())
DigitalClock
public class DigitalClock extends Animator { private Font font = new Font("Monospaced", Font.BOLD, 48); private Color color = Color.GREEN;
private int width, height;
public DigitalClock(int delay, int width, int height) {
super(delay);this.width = width;this.height = height;
} . . .
Continua…
DigitalClock
// metodo Hook public void paintComponent(Graphics g) { Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE); int second = calendar.get(Calendar.SECOND);
String time = “ ” + (hour / 10) + (hour % 10) + ":" + (minute / 10) + (minute % 10) + ":" + (second / 10) + (second % 10);
g.setFont(font); g.setColor(color); g.drawString(time, 50, 150); }} // chiude DigitalClock
DigitalClock
BouncingBall
public class BouncingBall extends Animator { // la pallina private Ellipse2D.Double ball; private final int DIAM = 30;
// ampiezza dell'oscillazione private int jump;
// posizione corrente private int x, y;
// direzione dela prossima oscillazione 1 = dx, -1 = sx private int dir = 1;
Continua…
BouncingBall
// costruttore public BouncingBall(int delay, int width, int height) {
super(delay);int lmargin = (int)(width * 0.1);int rmargin = (int)(width - DIAM - lmargin);jump = rmargin - lmargin;x = lmargin;y = (int)(height - DIAM) /3;ball = new Ellipse2D.Double(x,y,DIAM,DIAM);
}
Continua…
BouncingBall
// metodo Hook public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;// calcola nuova posizionex = x + dir * jump; // inverti la direzionedir = -dir; ball.setFrame(x,y,DIAM,DIAM);g2.setColor(Color.BLUE);g2.fill(ball);
}} // fine BouncingBall
Continua…
BouncingBall
Esempio: progetto di un plotter
• Ancora applicazione del pattern template
• Fattorizza il comportamento grafico in una classe astratta
• Lascia la definizione della funzione da tracciare alle sottoclassi concrete
• Vediamo la classe astratta Plotter due classi concrete PlotSine, PlotCosine
Continua…
Esempio: progetto di un plotter
Plotter
public abstract class Plotter extends JFrame{ . . .
public Plotter(Dimension dim) { . . . }
public void paintComponent(Graphics g) { drawCoordinates(g); plotFunction(g); }
protected void drawCoordinates(Graphics g) { . . . }
. . .
Continua…
Plotter
// metodo templateprotected void plotFunction(Graphics g) {
for (int px = 0; px < d.width; px++) { double x = (double)(px - xorigin) / (double)xratio; double y = func(x); int py = yorigin - (int) (y * yratio); g.fillOval(px - 1, py - 1, 3, 3);
}}
// metodo hookpublic abstract double func(double x);
} // end Plotter
I plotter concreti
• Implementano il metodo hook
public class PlotSine extends Plotter {
public double func(double x) { return Math.sin(x);}
}
public class PlotCosine extends Plotter {
public double func(double x) { return Math.cos(x);
}}
Un plotter multiplo
• Per il momento l’applicazione assegna ad ogni plotter una sola funzione
• Nuova funzionalità permettere il plotting di più funzioni
contemporaneamente
• Vincolo: ancora flessibilità: vogliamo disaccoppiare le funzioni
da tracciare dal plotter stesso
Soluzione con Template
public class DoublePlotter {
// due metodi Hook public abstract double func1(double x);public abstract double func2(double x); ...
}
public class TriplePlotter {
// tre metodi hookpublic abstract double func1(double x);public abstract double func2(double x);public abstract double func3(double x);
...}
Continua…
Soluzione con Template ?
• Poco elegante: una classe plotter per ciascun numero di funzioni da tracciare
• Poco flessibile: dobbiamo restringerci ad un numero fissato di funzioni, mentre vorremmo definire un MultiPlotter generico
• Troppi poco …
Soluzione con Strategy
• come Template disaccoppia le funzioni da tracciare dal plotter
• ma invece di rappresentare ogni funzione come un metodo, rappresenta come un oggetto
Oggetti Function
interface Function{ double apply(double x);}public class Sine implements Function{ public double apply(double x) { return Math.sin(x); }}public class Cosine implements Function { public double apply(double x) { return Math.cos(x); }}
• Sfruttiamo dynamic dispatch per invocare il metodo apply() corretto
Plotter
• Vediamo le conseguenze di questa idea nel progetto del plotter
Plotter
import javax.swing.*;
public abstract class Plotter extends JFrame {
. . . private Function fun;
public void paint(Graphics g) { drawCoordinates(g); plotFunction(g);
}
public Plotter (Dimension dim) { . . . }
. . . Continua…
Plotter
protected void plotFunction(Graphics g) {
for (int px = 0; px < d.width; px++) { double x = (double)(px - xorigin) / (double)xratio; double y = fun.apply(x); int py = yorigin - (int) (y * yratio); g.fillOval(px - 1, py - 1, 3, 3);
}}
} // end Plotter
Pattern Strategy
StrategyyA
metodoHook()
Strategy
metodoHook()
. . . strategy.metodoHook();. . .
Contesto
metodoDiContesto()
stragegy
StrategyB
metodoHook()
Pattern Strategy su Plotter
Sine
apply()
Function
apply()
. . . fun.apply();. . .
Plotter
plotFunction()
fun
Cosine
apply()
MultiPlotter
Continua…
• Ora generalizziamo per creare il plotter multiplo
MultiPlotterimport javax.swing.*;public abstract class MultiPlotter extends JFrame {
. . . private List<Function> fns = new ArrayList<Function>;private List<Color> colors = new ArrayList<Color>;
public void paint(Graphics g) { drawCoordinates(g); plotFunction(g);
}
public MultiPlotter (Dimension dim) { . . . }
public void addFunction(Function f, Color c) { fns.add(f); colors.add(c); } . . .
Continua…
MultiPlotter
...protected void plotFunction(Graphics g) { for (int i = 0; i < fns.size(); i++) {
if (fns.get(i) != null) { Color c = colors.tet(i); if (c != null) g.setColor(c); else g.setColor(Color.black); for (int px = 0; px < d.width; px++) { double x = (double) (px - xorigin) / (double) xratio; double y = fns.get(i).apply(x); int py = yorigin - (int) (y * yratio); g.fillOval(px - 1, py - 1, 3, 3);
}}} }
Classe concreta PlotSineCosine
public class PlotSineCosine extends MultiPlotter {
public PlotSineCosine() { // ricordate: Sine e Cosine implementano Function addFunction(new Sine(), Color.green); addFunction(new Cosine(), Color.blue);
}}
Caso di studio: animazione di algoritmi
• La struttura di animazione generica vista negli esempi del DigitalClock e BouncingBall precedenza non è sempre adeguata
• Nell’animazione di un algoritmo animazione deve essere gestibile dall’algoritmo chiamata a repaint(), deve essere controllata
dell’esecuzione, non ad istanti stabiliti dall’esterno
• Vogliamo comunque disaccoppiamento tra algoritmo e la struttura di animazione
Soluzione 1 – Template
SortAnimator
algorithm() sort()
AlgorithmAninator
animate()algorithm() . . .
algorithm(). . .
scramble()sort();. . .
BubbleSortAnimator
sort()
QuickSortAnimator
sort()
AlgorithmAnimator
public abstract class AlgorithmAnimator extends JComponent { public AlgorithmAnimator(int d) { delay = d; }
// metodi template: animate() & pause() public void animate() { algorithm(); }
final protected void pause() {try { Thread.currentThread().sleep(delay); } catch (InterruptedException e) { }repaint();
} // metodi hook: paintComponent() & algorithm() abstract protected void algorithm();
private int delay;}
SortAnimator
public class SortAnimator extends AlgorithmAnimator {
// l’array da ordinare protected int arr[]; private void swap(int a[], int i, int j) { int T; T = a[i]; a[i] = a[j]; a[j] = T; }
protected void scramble() { arr = new int[getPreferredSize().height / 6]; for (int i = arr.length; --i >= 0;) { arr[i] = (int)(i * Math.random());}
}
SortAnimator
// metodo hook protected void paintComponent(Graphics g) {
Dimension d = getSize(); g.setColor(Color.BLACK);g.fillRect(0, 0, d.width, d.height); g.setColor(Color.GREEN);int y = d.height - 10; double f = d.width / (double) arr.length; for (int i = arr.length; --i >= 0; y -= 5) { g.fillRect(0, y, (int)(arr[i] * f), 3);}
}
SortAnimator
// metodo hook final public void algorithm() { scramble(); JOptionPane.showMessageDialog(this, "Start animation"); sort(arr); JOptionPane.showMessageDialog(this,"Done! "); } // nuovo template protected abstract void sort(int[] a);
} // Chiude SortAnimator
BubbleSortAnimator
public class BubbleSortAnimator extends SortAnimator { public BubbleSortAnimator(int delay) {
super(delay); }
// override del metodo nella superclasse protected void sort(int[] a) {
for (int i = a.length; --i >= 0; ) for (int j = 0; j < i; j++) {
if (a[j] > a[j+1]) swap(a, j, j + 1); pause();
} }}
Valutazione
• Implementazione molto semplice
• Supporta diversi algoritmi
• Ma …• La classe SortAnimator è poco coesa• fornisce metodi legati ad aspetti algoritmici e di
visualizzazione
• Separare i due aspetti aumenta la flessibilità • nuova soluzione
Soluzione 2 – Strategy
SortAnimator
animate()
AlgorithmAninator
animate()pause()
. . . animator.pause(). . .
sorter.sort(). . .
BubbleSort
sort()
QuinckSort
sort()
SortingAlgorithm
sort()sorter
animator
AlgorithmAnimator
public abstract class AlgorithmAnimator extends JComponent { private int delay;
public AlgorithmAnimator(int delay){this.delay = delay;}
// template degenere, no hooks public abstract void animate();
final protected void pause() {try { Thread.currentThread().sleep(delay); } catch (InterruptedException e) { }repaint();
}}
SortAnimator
public class SortAnimator extends AlgorithmAnimator {
// l’array da ordinare private int arr[];
// l'algoritmo che esegue il sorting protected SortingAlgorithm sorter;
protected SortAnimator(int delay, SortingAlgorithm sorter) {
super(delay); this.sorter = sorter; sorter.setAnimator(this);
}
SortAnimator
public void animate() { scramble(); JOptionPane.showMessageDialog(this, "Start"); sorter.sort(arr); JOptionPane.showMessageDialog(this,"Done! "); }
// metodi scramble e paintComponent invariati . . . }
SortingAlgorithm
public abstract class SortingAlgorithm {
protected SortAnimator animator;
public setAnimator(SortAnimator animator) {this.animator = animator;
}
public abstract void sort(int[] a);
protected void swap(int a[], int i, int j) { . . . }}
BubbleSort
public class BubbleSort extends SortingAlgorithm { public void sort(int[] a) {
for (int i = a.length; --i >= 0; ) for (int j = 0; j < i; j++) {
if (a[j] > a[j+1]) swap(a, j, j + 1);// accesso protected al
// campo della superclasse animator.pause();
} }}
Valutazione
• La struttura della classe SortAnimator è migliore
• Ma … ci sono ulteriori misure per migliorare la coesione il metodo di visualizzazione dell’array può essere
separato dall’algoritmo e dal meccanismo di animazione
più flessibile
Soluzione 3 – Strategy2
SortAnimator
animate()
AlgorithmAninator
animate()pause()
BubbleSort
sort()
QuinckSort
sort()
SortingAlgorithm
sort()
theDisplayDisplayMethod
display()
DisplayMethod
display()
DisplayMethod
display()
ConfigSortAnimator
Soluzione 3 – Strategy2
• Classi AlgorithmAnimator, Sorting Animator
• Gerarchia SortingAlgorithm
• Invariate
ConfigurableSortAnimator
public class ConfigurableSortAnimator extends SortAnimator{ // il visualizzatore vero e proprio protected DisplayMethod theDisplay;
protected ConfigurableSortAnimator(int delay, SortingAlgorithm sorter, DisplayMethod display) {
super(delay,sorter);theDisplay = display;
}
protected void paintComponent(Graphics g) {Dimension d = getSize(); int[] a = getArray();theDisplay.display(a,d,g);
}}
DisplayMethod
public interface DisplayMethod{ public void display(int[] arr, Dimension d, Graphics g);}
public class VDisplay implements DisplayMethod { public void display(int[] arr, Dimension d, Graphics g) {
. . . }}
Frameworks
• Tipicamente: insieme di classi astratte ed interfacce
• Forniscono applicazioni semi-complete
• Da specializzare
• Progettate cercando di garantire i principi di astrazione e di favorire il riuso mediante l’applicazione di design patterns come quelli che abbiamo visto