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 iterator()... AbstractCollection iterator()... return new ConcreteIterator (this) ClientIterator next() hasNext() ConcreteIterator
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 classe class 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() helper
Una situazione più complessa class ContextA { void method(...) { ; stepA(); ; } class ContextB { void method(...) { ; stepB(); ; }
Generalizziamo la soluzione? class Common { commonCode1() { } commonCode2() { } } 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(...) { ; stepA(); ; } class ContextB { void method(...) { ; stepB(); ; }
Refactoring con il pattern Template abstract class Common { void methodoTemplate (...) { ; metodoHook(...); } 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 dellimmagine da animare alle sottoclassi concrete Vediamo la classe astratta Animator due classi concrete BouncingBall, DigitalClock
Animator public 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 posizione x = x + dir * jump; // inverti la direzione dir = -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 template protected 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 hook public 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 lapplicazione 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 hook public 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
MultiPlotter import javax.swing.*; public abstract class MultiPlotter extends JFrame {... private List fns = new ArrayList ; private List colors = new ArrayList ; 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 Nellanimazione di un algoritmo animazione deve essere gestibile dallalgoritmo chiamata a repaint(), deve essere controllata dellesecuzione, non ad istanti stabiliti dallesterno 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 { // larray 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 { // larray 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 dellarray può essere separato dallalgoritmo e dal meccanismo di animazione più flessibile
Soluzione 3 – Strategy 2 SortAnimator animate() AlgorithmAninator animate() pause() BubbleSort sort() QuinckSort sort() SortingAlgorithm sort() theDisplay DisplayMethod display() DisplayMethod display() DisplayMethod display() ConfigSortAnimator
Soluzione 3 – Strategy 2 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 lapplicazione di design patterns come quelli che abbiamo visto