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 AbstractCollection<E> Client iterator() . . . ConcreteCollection<E> iterator() . . . return new ConcreteIterator<E>(this) 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 classe class Computation // Dopo del refactoring { void private computeAll() { step1(); step2(); step3(); } void method1(...) { ... computeAll(); ... } void method2(...) { ... computeAll(); ... } } Refactoring di più classi • Codice duplicato su diverse classi class ComputationA { void method1(...) {...; step1(); step2(); step3(); ...} } class ComputationB { void method2(...) {...; step1(); step2(); step3(); ...} } 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 di più classi • Refactoring via aggregazione e delegation 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 ComputationB method1() method2() ComputationA method1() method2() Continua… Refactoring – UML • Refactoring via inheritance Common commonSteps() ComputationB method1() method2() ComputationA method1() method2() Continua… Refactoring – UML • Refactoring via aggregazione e delegation Helper commonSteps() helper helper ComputationB method1() method2() ComputationA method1() method2() 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 Generica metodoTemplate() metodoHook1(); metodoHook2(); classe Concreta methodHook1() methodHook2() . . . 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 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 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 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 Strategy Contesto stragegy metodoHook() metodoDiContesto() . . . strategy.metodoHook(); . . . StrategyyA metodoHook() StrategyB metodoHook() Pattern Strategy su Plotter Function Plotter fun apply() plotFunction() . . . fun.apply(); . . . Sine Cosine apply() apply() MultiPlotter • Ora generalizziamo per creare il plotter multiplo Continua… MultiPlotter import 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 AlgorithmAninator animate() algorithm() . . . algorithm() . . . SortAnimator algorithm() sort() BubbleSortAnimator sort() scramble() 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 AlgorithmAninator animate() pause() animator SortAnimator animate() sorter.sort() . . . sorter SortingAlgorithm sort() BubbleSort sort() QuinckSort sort() . . . animator.pause(). . . 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 AlgorithmAninator DisplayMethod animate() pause() display() SortAnimator ConfigSortAnimator theDisplay animate() DisplayMethod display() DisplayMethod display() SortingAlgorithm sort() BubbleSort sort() QuinckSort sort() 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