• Non ci sono risultati.

Software Engineering a.y Introduzione ai pattern di design (parte 3) Prof. Luca Mainetti Università del Salento

N/A
N/A
Protected

Academic year: 2022

Condividi "Software Engineering a.y Introduzione ai pattern di design (parte 3) Prof. Luca Mainetti Università del Salento"

Copied!
28
0
0

Testo completo

(1)

Software Engineering a.y. 2019-2020

Introduzione ai pattern di design (parte 3) Prof. Luca Mainetti

Università del Salento

(2)

Obiettivi della lezione

! Continuare lo studio di caso sull’editor di testi, al fine di introdurre i seguenti pattern

decorator: per decorare l’interfaccia utente (scrollbar, toolbar, ecc.)abstract factory: per avere differenti modelli di look-and-feel

bridge: per supportare differenti sistemi di window

(3)

Decorazioni dell’interfaccia utente

! Lexi utilizza varie “decorazioni” dell’interfaccia utente – i bordi della pagina

le barre di scorrimento

! L’obiettivo progettuale è quello di rendere semplice e dinamica l’aggiunta e la rimozione di tali decorazioni

! Potremmo utilizzare un approccio basato sull’ereditarietà, poiché comunque ci è necessario estendere il codice esistente

BorderedComposition = sottoclasse di Composition che aggiunge un bordo ad una Composition

ScrollableComposition = sottoclasse di Composition che aggiunge una barra di scorrimento ad una Composition

BorderedScrollableComposition = sottoclasse di Composition che aggiunge un bordo ed una barra di scorrimento ad una Composition

! Ma questo approccio non solo preclude gli interventi in esecuzione, ma fa esplodere il numero di classi necessarie per le decorazioni

(4)

Decorazioni dell’interfaccia utente (cont.)

! La composizione offre un meccanismo più flessibile

! Problema: quali oggetti devono essere composti?

– una decorazione viene applicata a un glyph, possiamo allora trattare la decorazione stessa come un oggetto (ad esempio, istanza della classe Border)

– i candidati per la composizione sono quindi Glyph e Border

! Problema successivo: chi compone e che contiene?

– se Glyph contiene Border dobbiamo rendere tutte le sottoclassi di Glyph consapevoli del Border

– se Border contiene Glyph ha senso poiché è così anche

nell’interfaccia utente. Inoltre tale scelta ha il vantaggio di delimitare tutto il codice per decorare con il bordo in una singola classe

(5)

Decorazioni dell’interfaccia utente (cont.)

! Il fatto che i bordi possono avere a loro volta un aspetto ci spinge a considerare le decorazioni dei particolari glyph (Border è una

sottoclasse di Glyph)

! In questo modo inoltre i client di glyph non dovrebbero trattare in modo differente i glyph decorati da quelli non decorati

! I client non sanno se stanno utilizzando un componente (ad esempio un glyph semplice) o un suo contenitore (ad esempio un glyph decorato), in particolare se il contenitore poi delega tutte le sue operazioni (ad

esempio il disegnarsi) al componente. Questo concetto è detto contenitore trasparente

! Il contenitore può anche estendere il comportamento componente, eseguendo operazioni prima e/o dopo la delega al componente stesso

! Il contenitore può aggiungere uno stato al componente

(6)

Decorazioni dell’interfaccia utente (cont.)

+Draw(in Window) Glyph

+Draw(in Window) MonoGlyph -component

1 1

+Draw(in Window) +DrawBorder(in Window)

Border

+Draw(in Window) Scroller

! Tutti i glyph che rappresentano

decorazioni sono contenitori trasparenti

! Aggiungiamo per questo scopo la classe MonoGlyph che rappresenta tutti i glyph con decorazioni

! MonoGlyph ha un riferimento ad un componente (unico) ed a questo inoltra tutte le richieste che riceve. E’ così

trasparente ai suoi client

! Ed esempio, così implementa l’operazione Draw

void MonoGlyph::Draw (Window* w) {

_component ->Draw(w);

}

(7)

Decorazioni dell’interfaccia utente (cont.)

! Le sottoclassi di MonoGlyph devono fornire

l’implementazione concreta di almeno una delle operazioni di delega al componente

! Ad esempio, Border::Draw prima invoca l’operazione del padre in modo da permettergli di disegnarsi (tutto il glyph componente ad eccezione del bordo), poi invoca

l’operazione privata DrawBorder per disegnare il bordo

void Border::Draw(Window* w) { MonoGlyph::Draw(w);

DrawBorder(w);

}

(8)

Decorazioni dell’interfaccia utente (cont.)

! Componiamo l’istanza di

Composition con un’istanza di

Scroller per aggiungere le barre di scorrimento e con un’istanza di Border per aggiungere il bordo

! A fianco è mostrata la struttura risultante

! Se invertiamo l’ordine di

composizione di Scroller e Border, otteniamo una pagina in cui scorre anche il bordo

! La composizione avviene tra un decoratore e un singolo glyph.

Complicheremmo inutilmente il

codice se la generalizzassimo a più glyph

colonna

riga riga

composition barre scorrimento

bordo

(9)

Il pattern Decorator

! Applica la tecnica del contenitore trasparente

! La decorazione non si riferisce solamente alle interfacce grafiche, ma a qualunque elemento che aggiunga

dinamicamente responsabilità ad un oggetto

! Ad esempio, provate ad ipotizzare qualche “decorazione semantica” della chat del WebTalk oppure degli avatar o degli oggetti condivisi

! I decoratori sono un’alternativa flessibile all’uso di sottoclassi come modo di estendere le funzionalità

! Il pattern è anche detto wrapper

(10)

Diversi standard di look-and-feel

! Si vuole facilitare il porting di Lexi su piattaforme hardware e software differenti

! Ogni piattaforma definisce il proprio standard di look-and-feel che

stabilisce come le applicazioni si presentano all’utente e in che modo reagiscono agli eventi

! Vogliamo che Lexi si adatti a look-and-feel diversi e che l’aggiunta di nuovi standard sia semplice

! Lexi utilizza il glyph per rappresentare sia oggetti visibili (caratteri, pulsanti, ecc.) sia oggetti invisibili (righe, colonne, ecc.)

! Gli standard di look-and-feel specificano come si comportano e come si visualizzano gli oggetti visibili (i widgets) che hanno responsabilità di controllo di altri glyph, cioè pulsanti, menu, scrollbar, ecc.

! I widget possono fare uso di glyph più semplici (caratteri, cerchi, poligoni) per presentare i dati

(11)

Diversi standard di look-and-feel (cont.)

! Potremmo avere un insieme di sottoclassi astratte di Glyph per ciascuna categoria di widget: ScrollBar, Button

! Per ciascuna sottoclasse astratta potremmo avere un insieme di

sottoclassi concrete che implementano i differenti standard di look-and- feel: MotifButton, PMButton, MacButton

! Quando Lexi dovrà visualizzare un pulsante in uno specifico look-and- feel dovrà istanziare la sottoclasse opportuna

! Lexi non può fare ciò con una chiamata diretta ad un costruttore, poiché sarebbe necessario inserire nel codice un particolare stile di pulsante, rendendo quindi impossibile modificare tale scelta in esecuzione

! Inoltre per portare Lexi su altre piattaforme sarebbe necessario modificare ogni costruttore

! Dobbiamo rendere astratto il processo di creazione degli oggetti

(12)

Diversi standard di look-and-feel (cont.)

! Ciò che vogliamo evitare è la chiamata diretta di costruttori

ScrollBar* sb = new MotifScrollBar;

! Supponiamo di fare nel seguente modo

ScrollBar* sb = guiFactory->CreateScrollBar();

! Dove guiFactory è un’istanza della classe MotifFactory, mentre CreateScrollBar() restituisce una nuova istanza della sottoclasse ScrollBar per lo specifico look-and-feel (Motif, in questo esempio)

! Per i client, l’effetto è il medesimo della chiamata diretta del costruttore, ma nel codice non ci sono più specifici

riferimenti a Motif

(13)

Diversi standard di look-and-feel (cont.)

! guiFactory astrae il processo di creazione per ogni look-and-feel ed anche per ogni tipologia di widget

! Grazie al fatto che MotifFactory è una sottoclasse di GUIFactory, che è una classe astratta che definisce l’interfaccia di creazione di glyph che rappresentano widget

! GUIFactory contiene tutte le operazioni per la creazione dei suoi prodotti

CreateScrollBar()CreateButton()ecc.

! Le sottoclassi di GUIFactory implementano tali operazioni per uno

specifico look-and-feel in modo da restituire oggetti che sono istanze di MotifScrollBar, PMScrollBar, MotifButton, PMButton, ecc.

! Una factory crea prodotti correlati. In questo esempio, crea prodotti che rispondono allo stesso look-and-feel

(14)

Diversi standard di look-and-feel (cont.) factory

+CreateScrollbar() +CreateButton() +CreateMenu() +...()

GUIFactory

+CreateScrollbar() +CreateButton() +CreateMenu() +...()

MotifFactory

+CreateScrollbar() +CreateButton() +CreateMenu() +...()

PMFactory

+CreateScrollbar() +CreateButton() +CreateMenu() +...()

MacFactory

return new MotifScrollBar return new MotifButton return new MotifMenu

return new PMScrollBar return new PMButton return new PMMenu

return new MacScrollBar return new MacButton return new MacMenu

(15)

Diversi standard di look-and-feel (cont.) prodotto della factory

Glyph

+ScrollTo() ScrollBar

+Press() Button

+Popup() Menu

+ScrollTo() MotifScrollBar

+ScrollTo() PMScrollBar

+ScrollTo() MacScrollBar

+Press() MotifButton

+Press() MacButton

+Press() PMButton

+Popup() MotifMenu

+Popup() MacMenu

+Popup() PMMenu

(16)

Diversi standard di look-and-feel (cont.)

! Ultimo problema da risolvere: da dove arriva l’istanza di GUIFactory?

! Può essere una variabile globale, un membro static di una classe, una variabile locale se l’intera interfaccia utente

viene creata all’interno di una classe singola

! Esiste anche il pattern Singleton che risolve il problema di creare una sola istanza di una classe, come in questo caso (il look-and-feel concreto è uno solo)

! In ogni caso, guiFactory deve essere inizializzata prima che sia usata per creare i widget e dopo avere deciso il look-

and-feel desiderato

(17)

Diversi standard di look-and-feel (cont.)

! Se il look-and-feel è noto prima della compilazione

GUIFactory* guiFactory = new MotifFactory;

! Se l’utente può specificare il look-and-feel da riga di comando

GUIFactory* guiFactory;

const char* styleName = getenv(“LOOK_AND_FEEL”);

if (strcmp(styleName, “Motif”) == 0) { guiFactory = new MotifFactory;

} else if (strcmp(styleName, “PM”) == 0) { guiFactory = new PMFactory;

} else {

guiFactory = new DefaultGUIFactory;

}

! Potremmo utilizzare una tabella che mappa le stringhe su

oggetti factory, “registrando” nuove sottoclassi factory

(18)

Il pattern Abstract Factory

! Fornisce un modo generale per creare famiglie di oggetti prodotto correlati senza istanziare direttamente le classi prodotto

! E’ utile quando il numero e la tipologia dei prodotti rimane costante nel tempo, e quando ci sono reali differenze tra le famiglie

! La scelta di una famiglia di prodotti specifica viene fatta istanziando una factory concreta

! Il pattern permette di cambiare a runtime la famiglia di

prodotti, sostituendo l’istanza della factory concreta con

l’istanza di un’altra factory

(19)

Diversi window system

! Una classe Window visualizza un glyph o una struttura di glyph sullo schermo. Ha le seguenti responsabilità, che sono tipiche dei window system

fornire operazioni per disegnare gli elementi graficiinconizzare, de-iconizzare

ridimensionare

disegnare il contenuto su richiesta (ad esempio, perché la finestra è stata de-inconizzata, una finestra sovrapposta è stata iconizzata, ecc.)

! Il problema è che il window system sono fatti da produttori di software differenti e possono essere molto diversi

la classe Window fornisce l’intersezione delle funzionalità: troppo limitatala classe Window fornisce l’unione delle funzionalità: troppo grande e

probabilmente inconsistente

(20)

Diversi window system (cont.)

+Draw(in Window) Glyph

+Redraw() +Iconify() +Lower() +...() +DrawLine() +DrawPolygon() +DrawText() +...()

Window

ApplicationWindow

+Iconify() IconWindow

+Lower() DialogWindow glyph>Draw(this)

-owner

owner>Lower() -glyph

! Adottiamo un approccio intermedio dove la classe astratta Window definisce le caratteristiche più

comuni ai diversi window system

! Le sottoclassi concrete di Window supportano le differenti tipologie di

finestre di un medesimo windows system

! In questo modo abbiamo un’astrazione uniforme di un qualsiasi window

system

(21)

Diversi window system (cont.)

! A questo punto come entrano in gioco le finestre dipendenti dalla piattaforma utilizzata?

! Poiché non stiamo implementando un nuovo window system, in qualche punto le nostre astrazioni dovranno essere implementate dal window system

sottostante. Dove sono tali implementazioni?

! Potemmo implementare più versioni di Window e delle sue sottoclassi per ciascun window system. La corretta versione dovrà essere scelta all’atto della compilazione di Lexi per la corretta piattaforma

! Potremmo creare per ogni sottoclasse di Window delle altre sottoclassi che forniscono le diverse implementazioni per i differenti window system

! Nel primo caso abbiamo un problema di tenere traccia della corretta configurazione

! Nel secondo caso abbiamo un problema di esplosione del numero di classi

! In entrambi i casi non possiamo cambiare il window system in uso dopo che Lexi è stato compilato. Abbiamo diversi eseguibili

! La soluzione ancora una volta sta nell’incapsulare ciò che cambia, in questo caso l’implementazione del window system

(22)

Diversi window system (cont.)

+Redraw() +DrawRect(in ...)

Window

ApplicationWindow IconWindow DialogWindow

+DeviceRedraw() +DeviceRect(in ...)

WindowImp

+DeviceRedraw() +DeviceRect(in ...)

MacWindowImp -imp

+DeviceRedraw() +DeviceRect(in ...)

XWindowImp

+DeviceRedraw() +DeviceRect(in ...)

PMWindowImp

! Definiamo una gerarchia di classi separata che ha come radice la classe

astratta WindowImp che fornisce un’interfaccia per differenti implementazioni di window system

! Un oggetto window deve essere inizializzato con un riferimento ad un’istanza della sottoclasse di WindowImp che corrisponde alla piattaforma desiderata

(23)

Diversi window system (cont.)

! Le sottoclassi di WindowImp ricevono richieste e le convertono in operazioni specifiche del particolare window system

! Ad esempio, un Rectangle delega il disegno ad una Window

void Rectangle::Draw (Window* w) { w->DrawRect(_x0, _y0, _x1, _y1);

}

! L’implementazione di DrawRect usa l’operazione astratta DeviceRect() di WindowImp

void Window::DrawRect (Coord x0, y0, x1, y1) { _imp->DeviceRect(x0, y0, x1, y1);

}

dove _imp è una variabile membro di Window che memorizza l’istanza della sottoclasse di WindowImp con cui è stata configurata

(24)

Diversi window system (cont.)

! Per la classe XWindowImp l’implementazione di DeviceRect() può essere la seguente

void XWindowImp::DeviceRect (Coord x0, y0, x1, y1) { int x = round(min(x0, x1));

int y = round(min(y0, y1));

int w = round(abs(x0 - x1));

int h = round(abs(y0 – y1));

XDrawRectangle(_dpy, _winid, _gc, x, y, w, h);

}

dove XDrawRectangle() è l’operazione fornita dalla piattaforma X per disegnare un rettangolo intermini del suo vertice inferiore di sinistra, della sua lunghezza e della sua altezza

! Altre classi concrete potrebbero implementare l’operazione DeviceRect() in modo profondamente differente

(25)

Diversi window system (cont.)

! Poiché PM non definisce operazioni esplicite per disegnare rettangoli, l’implementazione di DeviceRect() in PMWindowImp può essere

void PMindowImp::DeviceRect (Coord x0, y0, x1, y1) { Coord left = min(x0, x1);

Coord right = max(x0, x1);

Coord bottom = min(y0, y1);

Coord top = max(y0, y1);

PPOINTL point[4];

point[0].x = left; point[0].y = top;

point[1].x = right; point[1].y = top;

point[2].x = right; point[2].y = bottom;

point[3].x = left; point[3].y = bottom;

if ((GpiBeginPath(_hps, 1L) == false) ||

(GpiSetCurrentPosition(_hps, &point[3]) == false) ||

(GpiPolyLine(_hps, 4L, point) == GPI_ERROR) ||

(GpiEndPath(_hps) == false)) { // segnala errore

} else { GpiStrokePath(_hps, 1L, 0L); }

(26)

Diversi window system (cont.)

! Una window richiede di essere inizializzata in modo appropriato con un’istanza della classe WindowImp. Come facciamo ad inizializzare correttamente _imp?

! Possiamo definire una classe WindowSystemFactory che fornisce un’abstract factory per la creazione di differenti tipologie di oggetti che forniscono

l’implementazione nelle diverse piattaforme Class WindowSystemFactory {

public:

virtual WindowImp* CreateWindowImp() = 0;

virtual ColorImp* CreateColorImp() = 0;

virtual FontImp* CreateFontImp() = 0;

};

! Creiamo poi una factory concreta per ogni window system

Class PMWindowSystemFactory : public WindowSystemFactory {public:

virtual WindowImp* CreateWindowImp() { return new PMWindowImp; }

……… };

(27)

Diversi window system (cont.)

! Altra factory concreta

Class XWindowSystemFactory : public WindowSystemFactory {

public:

virtual WindowImp* CreateWindowImp() { return new XWindowImp; }

……… };

! Il costruttore di Window inizializza _imp con l’istanza di WindowImp appropriata

Window::Window () {

_imp = windowSystemFactory->CreateWindowImp();

};

! La variabile windowSystemFactory referenzia un’istanza nota di una sottoclasse di WindowSystemFactory

(28)

Il pattern Bridge

! L’interfaccia Window tiene conto del programmatore applicativo

! L’interfaccia WindowImp tiene conto del programmatore sistemista e dei differenti window system

! Le relazioni tra Window e WindowImp sono un esempio di applicazione del pattern Bridge

! Tale pattern ha lo scopo di consentire a gerarchie separate di classi di cooperare anche se evolvono in maniera indipendente

! Il pattern Bridge ci permette di mantenere e far evolvere le finestre

logiche senza dover agire sul codice specifico delle finestre del window system

! Il pattern Bridge disaccoppia quindi un’astrazione dalla sua

implementazione, permettendo di farle evolvere indipendentemente

! Utile quando un’astrazione può avere più implementazioni diverse

Riferimenti

Documenti correlati

Manifestazione di interesse per l’invito a successiva procedura negoziata, mediante cottimo fiduciario per servizi in economia, ex articolo 125, comma 11, del

h) di aver – di non aver (1) riportato condanne penali e non essere destinatario di provvedimenti che riguardano l’applicazione di misure di prevenzione, di

Visto l’avviso di mobilità intra-aziendale di CA pubblicato sul sito dell’ASP TRAPANI chiede di partecipare alla convocazione di mobilità intra-aziendale di CA. Dichiara

- Di non avere riportato condanne penali e di non avere procedimenti penali in corso (in caso positivo dichiarare le condanne penali subite o gli eventuali procedimenti penali

445: Le istanze e le dichiarazioni sostitutive dell'atto di notorietà da produrre agli organi della amministrazione pubblica o ai gestori o esercenti di pubblici servizi

il conferimento di ore di PPI ed Attività Distrettuale resisi vacanti. A tal’uopo il sottoscritto consapevole che in caso di mendaci dichiarazioni saranno applicate le sanzioni

_____ del ________ pubblicato nel sito dell’ASP Trapani, l’inserimento nella Graduatoria Aziendale Guardia Medica Turistica per l’anno 2012 per il conferimento di incarichi

consapevole che per poter essere ammesso alla definizione agevolata dovrà, a pena di improcedibilità di questa istanza, provvedere entro il 31 maggio 2018 al versamento di tutte