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
Scarica

11CaseStudies