Pattern -Singleton Il Pattern Singleton assicura

Transcript

Pattern -Singleton Il Pattern Singleton assicura
Pattern
-Singleton
Il Pattern Singleton assicura che una classe abbia solo una istanza e provvede un punto globale per
accedervi.
Quindi c'è la necessita che la classe abbia un meccanismo opportuno che gestisca un unica istanza
della classe stessa e che nello stesso tempo impedisca la creazione idipendente di istanze(oggetti) da
altre classi
ilnoltre si deve prevedere solo un unico metodomediante il quale ottenere l'istanza
Per far funzionare correttamente il pattern singleton dovremo fare:
-costruttore pubblico che al suo interno andremo a creare una nuov istanza del costruttore privato
che si trova sempre all'interno della classe
-dichiarare la classe final per prevenire la sovrascrittura di metodi da parte di sottoclassi
-sovrascrivere il metodo clone() (solo se la classe implementa Cloneable) facendogli lanciare un
eccezione CloneNotSupportedException
se questa è ereditato da altre classe
final public class Singleton {//classe dichiarata final cosi facendo non è modificabile da sottoclassi
private static Singleton instance = null;//all'inizio l'istanza è null
//costruttore privato cosi è utilizzabile solo dalla classe
private Singleton() {
}
public static Singleton getInstance() {//metodo per richiedere un istanza . Punto globale per
accedervi
if(instance == null) {//se è null allora non ho creato ancora nessun istanza
instance = new Singleton();//creazione dell'istanza
}
return instance;//ritorna l'istanza creata se è null oppure ritorna l'istanza che avevo già creato
}
}
Variante : creo l'istanza nel momento di loader (avvio) del programma
final public class Singleton {
private static Singleton instance = new Singleton();// creo già l'istanza
private Singleton() {costruttore
}
//metodi
public static Singleton getInstance() {
return instance; //restituisce l'istanza che avevo creato nell'avvio
}
}
Variante: si hanno tot tipi di istanze che sono già create al momento dell'avvio(fase di loader)
final public class Singleton {
// num di tipi di istanze
public static final int TIPO_UNO = 1;
public static final int TIPO_DUE = 2;
public static final int TIPO_TRE = 3;
// creazione istanze
private static Singleton instance1 = new Singleton(TIPO_UNO);
private static Singleton instance2 = new Singleton(TIPO_DUE);
private static Singleton instance3 = new Singleton(TIPO_TRE);
//costruttore
private Singleton(int tipo) {
// inizializzazione dell'istanza in funzione del paramentro in funzione del parametro
}
// metodo che restituisce il singleton istanziati all'avvio.il tipo di istanza restituita dip dal tipo
public static Singleton getInstance(int tipo) throws Exception {
Singleton instance;
if (tipo == TIPO_UNO)
instance = instance1;
else if (tipo == TIPO_DUE)
instance = instance2;
else if (tipo == TIPO_TRE)
instance = instance3;
else
throw new Exception("Tipo dell'istanza non riconosciuto!");
return instance;// restituisce la istanza
}
precisazioni:
questi casi che abbiamo visto sono solo utilizzabili nella programmazione uni thread
se volessimo utilizzare nella programmazione multithread dovremo utilizzare synchronized per
garantire la presenza di una istanza
potremo però anche decidere di creare un certo numero di istanze in questo caso basterebbe un
contatore .
-Factory Method (http://www.shedidntcome.com/cavallorama/2009/06/30/factory/)
Definire un’interfaccia per creare un oggetto ma lasciare che siano le sottoclassi a decidere quale
oggetto creare. Factory Method fa in modo che una classe possa delegare alle sottoclassi la
creazione di un nuovo oggetto.
Nello schema generico, esistono due entità astratte (Creator e Product) che nel nostro esempio
corrispondono rispettviamente a Pasticceria e Torta. Queste entità non dipendono l’una dall’altra,
sono cioè disaccoppiate. In particolare, Product definisce l’interfaccia del prodotto da creare mentre
Creator dichiara il vero e proprio FactoryMethod(). L’implementazione ConcreteCreator provvede a
creare e ritornare un’istanza concreta del prodotto (ConcreteProduct). Anche da questo schema
astratto è possibile comprendere il passaggio chiave di factory Method design pattern e cioè che
Creator delega alle sue sottoclassi (o implementazioni) le decisioni sulla creazione degli oggetti.
Dovremo creare un classe astratta dove risiede il costruttore astratto
questo comporta che le sottoclassi(ovvero chi estenderà la sottoclasse ) dovrà implementare i
metodi astratti
In tal modo si vuole delegare alle sottoclassi di Pasticceria la creazione degli oggetti, cioè la
decisione su quale oggetto effettivamente creare.
public abstract class Pasticceria {
Torta ordinaTorta(TortaEnum tipo) {
Torta torta = null;
torta = creaTorta(tipo);
torta.prepara();
torta.inforna();
torta.guarnisci();
torta.confeziona();
return torta;
}
abstract Torta creaTorta(TortaEnum tipo);
}
//sottoclasse dove andremo a estendere la classe astratta e implementare i metodi astratti
public class PasticceriaBellavista extends Pasticceria{
// implementazione del costruttore crea torta che era stato messo astratto . Cosi facendo ogni
pasticceria avrà a disposizioni diverse torte da poter creare con stili diversi ovvero viene delagato la
decisione di quale torta creare alla sua sottoclasse
Torta creaTorta(TortaEnum tipo) {
Torta torta = null;
switch (tipo){
case CHEESECAKE: torta = new Cheesecake();
break;
case PASTIERA: torta = new SquisitissimaPastieraBellavistaDOP();
break;
case SACHER: torta = new Sacher();
break;
case SBRISOLONA: torta = new Sbrisolona();
break;
case TORTABABA: torta = new LaTortababaDiBellavista();
break;
}
return torta;
}
}
N.B torta a sua volta sarà un interfaccia astratta cosi facendo potremo andare ad crere nuove torte
solamente implementandp l'interfaccia alla classe che la estende . Cosi potremo avere più tipi di
torta con in comune solo un interfaccia
-Strategy
Definire una famiglia di algoritmi, incapsulare ciascuno di essi e renderli interscambiabili. Strategy
fa in modo che gli algoritmi possano variare indipendentemente dai client che li utilizzano.
Si crea un interfaccia comune con un metodo che verrà implementato nella sottoclassi
nel context(dove andremo a invocare i costruttori e i metodi delle sottoclassi ) andremo ad usare un
costruttore dell'interfaccia , cosi facendo avremo un costruttore comune per tutte le classi che
implementano l'interfaccia
quando andremo a creare un nuovo oggetto (di una classe che ha implementato l'interfaccia)
andremo ad asegnare il nuovo oggetto al costruttore del interfaccia presente nel context con un
metodo di assegnamento esempio setDisplay format
per capire meglio
dove array display format è la Strategy ovvero un Interfaccia comune per tutti gli algorimi
supportati . Questa interfaccia viene usata da MyArrayper invocare algoritmi definiti nelle classi
che implementano l'iinterfaccia.
public class MyArray {
private int[] array;
private int size;
ArrayDisplayFormat format;
public MyArray( int size ) {
array = new int[ size ];
}
public void setValue( int pos, int value ) {
array[pos] = value;
}
public int getValue( int pos ) {
return array[pos];
}
public int getLength( int pos ) {
return array.length;
}
public void setDisplayFormat( ArrayDisplayFormat adf ) {
format = adf;
}
public void display() {
format.printData( array );
}
}
Nella figura si vede che:
ArrayDisplayFormat format è il costruttore del interfaccia che determina la strategia di stampa
ovvero questo è il costruttore è comune per tutte le sottoclassi ed verrà assegnato il costruttore della
sottoclasse che voglio usare . Questo servirà per cambiare la modalità di stampa
SetDisplayFormat metodo utilizzato per cambiare la modalità di stampa . Si noti che andiamo ad
passare un oggetto (creato con un costruttore di una sottoclasse ) ad l'oggetto comune di tutte le
sottoclassi.
Infine ci sarà il metodo display che andrà ad utilizzare il metodo del oggetto format
ogni sottoclasse andrà ad implementare il propio metodo di stampa e nel main per cambiare il
metodo di stampa basterà creare un oggetto della sottoclasse e passarlo alla metodo
SetDisplayFormat
public interface ArrayDisplayFormat {
public void printData( int[] arr );
}
Le strategie di stampa sono implementate nelle classi StandardFormat (per il formato
“{ a, b, c, … }”) e MathFormat (per il formato “Arr[0]=a Arr[1]=b Arr[2]=c …”):
public class StandardFormat implements ArrayDisplayFormat {
public void printData( int[] arr ) {
System.out.print( "{ " );
for(int i=0; i < arr.length-1 ; i++ )
System.out.print( arr[i] + ", " );
System.out.println( arr[arr.length-1] + " }" );
}
}
public class MathFormat implements ArrayDisplayFormat {
public void printData( int[] arr ) {
for(int i=0; i < arr.length ; i++ )
System.out.println( "Arr[ " + i + " ] = " + arr[i] );
}
}
public class StrategyExample {
public static void main (String[] arg) {
MyArray m = new MyArray( 10 );
m.setValue( 1 , 6 );
m.setValue( 0 , 8 );
m.setValue( 4 , 1 );
m.setValue( 9 , 7 );
System.out.println("This is the array in ’standard’ format");
m.setDisplayFormat( new StandardFormat() );
m.display();
System.out.println("This is the array in ’math’ format:");
m.setDisplayFormat( new MathFormat() );
m.display();
}
}
Esecuzione dell’esempio
This is the array in ’standard’ format :
{ 8, 6, 0, 0, 1, 0, 0, 0, 0, 7 }
This
Arr[
Arr[
Arr[
Arr[
Arr[
Arr[
Arr[
Arr[
Arr[
Arr[
is the array in ’math’ format:
0 ] = 8
1 ] = 6
2 ] = 0
3 ] = 0
4 ] = 1
5 ] = 0
6 ] = 0
7 ] = 0
8 ] = 0
9 ] = 7
-State
Consente ad un oggetto di modificare il suo comportamento quando il suo stato interno cambia.
Il pattern state suggerisce di creare delle classe che incapsulano le operazioni che valgono in un
determinato stato . Per questo Ogni classe (ConcreteState) rappresenta un singolo stato possibile del
Context e implementa una interfaccia comune (State) contenente le operazioni che il Context delega
allo stato.quindi potremo avere più ConcreteState in base al numero di stati possibili .
N.B Il Context deve contenere al suo interno un riferimento al ConcreteState che rappresenta lo
stato corrente
Quindi ogni classe (ConcreteState) implementa il comportamento associato ad uno stato del
Context.
La logica che implementa il cambiamento di stato viene implementata in una sola classe (Context)
piuttosto che con istruzioni condizionali (if o switch) nella classe che implementa il
comportamento.
Esempio dell'orologio
La classe astratta ClockState definisce l'interfaccai che ogni ConcreteState deve implementare.
public abstract class ClockState {
protected Clock clock ;
public ClockState(Clock clock) {
this.clock = clock;
}
public abstract void modeButton();//metodi da implementare
public abstract void changeButton();
}
//sottoclasse che implementa la classe astratta stato del NormalDisplay
public class NormalDisplayState extends ClockState {
public NormalDisplayState(Clock clock) {
super( clock );// richiamo il costruttore della superclasse
System.out.println( "** Clock is in normal display.");
}
public void modeButton() {
clock.setState( new UpdatingHrState( clock ) );//metodo per cambiare lo stato
}
public void changeButton() {
System.out.print( "LIGHT ON: " );
clock.showTime();//richiama il metodo showtime implementato nel context
}
}
// sottoclasse che implementa la classe astratta . Stato che aumenta le ore
public class UpdatingHrState extends ClockState {
public UpdatingHrState(Clock clock) {
super( clock );//richiamo il costruttore della superclasse
System.out.println(
"** UPDATING HR: Press CHANGE button to increase hours.");
}
public void modeButton() {
clock.setState( new UpdatingMinState( clock ) );//cambio di stato
}
public void changeButton() {//metodo che incrementa le ore
clock.hr++;
if(clock.hr == 24)
clock.hr = 0;
System.out.print( "CHANGE pressed - ");
clock.showTime();
}
}
// sottoclasse che implementa la classe astratta . Stato che aumenta i minuti
public class UpdatingMinState extends ClockState {
public UpdatingMinState(Clock clock) {
super( clock );
System.out.println(
"** UPDATING MIN: Press CHANGE button to increase minutes.");
}
public void modeButton() {
clock.setState( new NormalDisplayState( clock ) );//metodo che cambia lo stato e
passa allo stato normalDisplay
}
public void changeButton() {//metodo che incrementa i minuti
clock.min++;
if(clock.min == 60)
clock.min = 0;
System.out.print( "CHANGE pressed - ");
clock.showTime();
}
}
// Context
public class Clock {
private ClockState clockState;//costruttore dell'interfaccia comune che permette di cambiare
lo stato dell'oggetto cosi posso cambiare i comportamenti dei metodi
public int hr, min;
public Clock() {
clockState = new NormalDisplayState( this );//creo l'orologio e parte con lo stato
normal display
}
public void setState( ClockState cs ) {//viene passato già lo stato ovvero l'oggetto che
clockState = cs;
rappresenta lo stato
}
public void modeButton() {//chiamata dei metodi dello stato che era passato quindi i loro
clockState.modeButton();
comportamenti dipendono dallo stato
}
public void changeButton() {
clockState.changeButton();
}
public void showTime() {
System.out.println( "Current time is Hr : " + hr +
" Min: "
+ min );
}
}
//Main
public class StateExample {
public static void main ( String arg[] ) {
Clock theClock = new Clock();//creazione dell'orologio
theClock.changeButton();//sic nello stato normal mostra l'ora
theClock.modeButton();//passo alla modalità incremento ora
theClock.changeButton();//incremento l'ora
theClock.changeButton();//incremento l'ora
theClock.modeButton();//cambio stato e passo ai minuti
theClock.changeButton();//incremento i minuti
theClock.changeButton();//incremento i minuti
theClock.changeButton();//incremento i minuti
theClock.changeButton();//ritorno alla modalità normale
theClock.modeButton();//visualizzo l'ora
}
}
Questo modo di implemenatre con il pattern state permette di aggiungere dei nuovi stati in modo
semplice andando a implementare solamente una nuova classe che rappresenta lo stato ed modifica
il changestate di due classe che dovranno passare adesso per il nuovo stato
Decorator
Lo scopo principale di questo pattern è quello di aggiungere dinamicamente delle funzionalità alle
classi evitando una profilerazione di sottoclassi.
Nell'immagine appena riportata potete vedere l'interfaccia Component, che è appunto il nostro
componente base, il quale definisce soltato il metodo operation(). La classe che implementa questa
interfaccia è ConcreteComponent, dove troveremo la prima definizione di operation(). A questo
punto, per aggiungere delle funzionalità a questo Component possiamo utilizzare il pattern
Decorator, definendo una classe astratta Decorator(anche un'interfaccia) che implementa
Component ma che in più ha un riferimento a Component.
A primo avviso può sembrare una cosa strana, d'altronde sto implementando un'interfaccia e ho un
attributo che è della stessa tipologia. Vedendo la classe che estende questo Decorator, ovvero
ConcreteDecorator, riusciamo a capire meglio a cosa serve l'attributo di tipo Component.
Praticamente ConcreteDecorator, estendendo Decorator, implementa il metodo operation() e al suo
interno eseguire un codice di questo tipo:
In questo modo noi "decoriamo" il comportamento del metodo operation() di Component con un
nostro metodo (addedBehaviour()).
Esempio
Supponiamo di avere un oggetto di tipo Ingegnere a cui vogliamo aggiungere delle responsabilità:
vogliamo farlo diventare
capoufficio e
responsabile di un progetto
Impiegato
# im pie ga to
g e tN am e ()
g e tOf fice()
0 ..1
w ho Is()
Ingegnere
Responsab ile
im pie ga to : Im p ie ga to
g e tN am e ()
g e tOf fic e()
g e tN am e ()
w ho Is()
g e tOf fice()
w ho Is()
Cap oUfficio
sa yIa m Boss ()
w ho Is()
ProjectMan ag er
p rog e tto : S tring
g e tP ro ge tt o()
se tP ro g et to ()
//interfaccia dell'impiegato
public interface Impiegato {
String getName();//metodi che devono essere implem
String getOffice();
void whoIs();
}
//classe ingnere che dovrà essere decorata
public class Ingegnere implements Impiegato {
private String name, office;
public Ingegnere( String nam, String off ) {//costruttore
name = nam;
office = off;
}
//metodi
public String getName() {
return name ;
}
public String getOffice() {
return office ;
}
public void whoIs() {
System.out.println( "Sono " + getName() + ", dell'ufficio "
+ getOffice() +".");
}
}
//classe decorator . È una classe astratta quindi si dovrà implementare i metodi astratti
public abstract class Responsabile implements Impiegato {
protected Impiegato impiegato;
public Responsabile(Impiegato imp) {
impiegato = imp;
}
public String getName() {
return impiegato.getName();
}
public String getOffice() {
return impiegato.getOffice();
}
public void whoIs() {
impiegato.whoIs();
}
}
//decoratore Capoufficio che va ad estendere la classe astratta responsabile
// Le responsabilità particolari che riguardano le funzioni di un capoufficio sono
// codificate nella classe CapoUfficio. Questa classe estende le funzioni del
// Decorator, aggiungendo l’operazione sayIamBoss(), che viene chiamata come
// parte della ridefinizione del metodo whoIs()
public class CapoUfficio extends Responsabile {
public CapoUfficio( Impiegato imp ) {
super( imp );//richiamo il costruttore della superclasse ovveri capoUfficio
}
public void whoIs() {//ridifinisce una parte di questo metodo
sayIamBoss();
super.whoIs();
}
private void sayIamBoss(){nuovo metodo che va a decorare
System.out.print( "Sono un capoufficio. " );
}
}
//classe cha va ad estendere la classe astratta Responsabile
public class ProjectManager extends Responsabile {
private String progetto;
public ProjectManager(Impiegato imp, String proj) {
super(imp);//richiamo costruttore della superclasse Responsabile
progetto = proj;
}
public void whoIs() {
super.whoIs();
System.out.println( "Sono il Manager del Progetto:" + progetto );
}
}
//Main
public class DecoratorEsempio1 {
public static void main(String arg[]) {
Impiegato saraFamoso = new Ingegnere( "Franco Rossi","Ufficio
Programmazione");//costruisco oggetto
System.out.println( "Chi sei?");
saraFamoso.whoIs();//senza decoratore
saraFamoso = new CapoUfficio( saraFamoso );//nuovo decoratore
System.out.println( "...e adesso chi sei?");
saraFamoso.whoIs();//chiamo metodo del decoratore
saraFamoso = new ProjectManager( saraFamoso,
"D.O.S.- Doors Operating System" );//nuovo decoratore
System.out.println( "...e adesso chi sei?");
saraFamoso.whoIs();//chiamo metodo del decoratore
saraFamoso = new ProjectManager( saraFamoso,
"N.I.E. - New Internet Explorer" );//nuovo decoratore
System.out.println( "...e adesso chi sei?");
saraFamoso.whoIs();//chiamo metodo del decoratore
}
}
//Esecuzione
Chi sei?
Sono Franco Rossi, dell'ufficio Ufficio Programmazione.
...e adesso chi sei?
Sono un capoufficio. Sono Franco Rossi, dell'ufficio Ufficio Programmazione.
...e adesso chi sei?
Sono un capoufficio. Sono Franco Rossi, dell'ufficio Ufficio Programmazione.
Sono il Manager del Progetto:D.O.S.- Doors Operating System
...e adesso chi sei?
Sono un capoufficio. Sono Franco Rossi, dell'ufficio Ufficio Programmazione.
Sono il Manager del Progetto:D.O.S.- Doors Operating System
Sono il Manager del Progetto:N.I.E. - New Internet Explorer
-Composite
In Java, può succedere di trovarsi di fronte alla necessità di gestire gerarchie di oggetti, all'interno
delle quali possiamo trovare oggetti semplici e altri che fanno da contenitore ad oggetti più
complessi.
Da questo punto di vista possiamo avere classi che rappresentano concetti semplici come il
segmento, la circonferenza e quant'altro. Le classi complesse in questo caso possono essere quelle
che non fanno altro che comporre più classi semplici al loro interno, includendole per disegnare
qualcosa di più complesso della semplice figura geometrica
Esempio
Nel magazzino di una ditta fornitrice di computer ci sono diversi prodotti, quali :
computer pronti per la consegna
pezzi di ricambio (o pezzi destinati alla costruzione di nuovi computer).
Dal punto di vista della gestione del magazzino, alcuni di questi pezzi sono
-pezzi singoli (indivisibili),
-pezzi composti da altri pezzi.
Ad esempio,
“monitor”, “tastiera” e “RAM” sono pezzi singoli,
“main system”, è un pezzo composto (“processore”, “disco rigido” e “RAM”).
Component
- children
name: String
Component()
addComponent()
removeComponent()
describe()
getChild()
ParteSingola
0..*
ParteComposta
children: Vector
ParteSingola()
describe()
ParteComposta()
addComponent()
describe()
getChild()
removeComponent()
La classe astratta component deve essere estesain una che rappresenta i singoli componenti e un
altra che rappresenta i componenti composti. Quindi ParteComposta sarà un contenitore di
componenti quindi permette al suo interno di immagazinare sia componenti singoli che componenti
complessi(ovvero altri contenitori)
Il campo children della classe Composite può essere visto come un vettore o una LinkedList o una
qualsiasi struttura dinamica che permette di immagazzinare degli oggetti
Composite.operation() richiama operation() di ogni oggetto contenuto in children
//classe astratta dei componenti
public abstract class Component {
public String name;
public Component(String aName){//costruttore
name = aName;
}
public void addComponent(Component c) throws ParteSingolaException {//metodi che
if (this instanceof ParteSingola)
vengono ridefiniti nelle
throw new ParteSingolaException( );
sottoclassi
}
public void removeComponent(Component c) throws ParteSingolaException{
if (this instanceof ParteSingola)
throw new ParteSingolaException( );
}
public Component getChild(int n){
return null;
}
public abstract void describe();
}
//classe delle parti composte
public class ParteComposta extends Component {
private Vector children; //vettore contenente le parti singole e le parti composte ovvero è un
contenitore
public ParteComposta(String aName) {
super(aName);//richiamo il costruttore della superclasse
children = new Vector();
}
public void addComponent(Component c) throws ParteSingolaException {
children.addElement(c);
}
public void removeComponent(Component c) throws ParteSingolaException{
children.removeElement(c);
}
public Component getChild(int n) {
return (Component)children.elementAt(n);
}
public void describe(){//descrittore di tutti i componenti che compongono l'oggetto
composto
System.out.println("Component: " + name);
System.out.println("Composto da:");
System.out.println("{");
int vLength = children.size();
for( int i=0; i< vLength ; i ++ ) {
Component c = (Component) children.get( i );
c.describe();//chiamo il metodo descrittore della parti singole
}
System.out.println("}");
}
}
//classe della parti singole che estendono al classe astratta Component
public class ParteSingola extends Component {
public ParteSingola(String aName) {
super(aName);//richiamo il costruttore della superclasse
}
public void describe(){
System.out.println( "Component: " + name );//descrittore della parte singola
}
}
//main
public class CompositeEsempio1 {
public static void main(String[] args) {
//
Creo le parti singole
Component monitor = new ParteSingola("LCD Monitor");
Component keyboard = new ParteSingola("Italian Keyboard");
Component processor = new ParteSingola("Pentium 4 Processor");
Component ram
= new ParteSingola("512 MB RAM");
Component hardDisk = new ParteSingola("60 Gb Hard Disk");
//
Creo un oggetto composto con 3 parti singole
Component mainSystem = new ParteComposta( "Main System" );
try {//aggiungo le parti singole all'oggetto composto
mainSystem.addComponent( processor );
mainSystem.addComponent( ram );
mainSystem.addComponent( hardDisk );
}
catch (ParteSingolaException e){
e.printStackTrace();
}
//
Creo un oggetto composto da parti singole e composte
Component computer = new ParteComposta("Computer");
try{//aggiungo all'oggetto le pati singole e composte
computer.addComponent( monitor );
computer.addComponent( keyboard );
computer.addComponent( mainSystem );
}
catch (ParteSingolaException e){
e.printStackTrace();
}
System.out.println("**Descrivo il component ’monitor’:");
monitor.describe();//chiamo il metodo descrittore delle parti composte
System.out.println("**Descrivo il component ’main system’:");
mainSystem.describe();//chiamo il metodo descrittore delle parti composte
System.out.println("**Descrivo il component ’computer’:" );
computer.describe();//chiamo il metodo descrittore delle parti composte
}
}
//Esecuzione
**Descrivo il component ’monitor’:
Component: LCD Monitor
**Descrivo il component ’main system’:
Component: Main System
Composto da:
{
Component: Pentium 4 Processor
Component: 512 MB RAM
Component: 60 Gb Hard Disk
}
**Descrivo il component ’computer’:
Component: Computer
Composto da:
{//componenti che costituiscono il computer
Component: LCD Monitor
Component: Italian Keyboard
Component: Main System
Composto da:
{//componenti che costituiscono il Main System
Component: Pentium 4 Processor
Component: 512 MB RAM
Component: 60 Gb Hard Disk
}
}
-Iterator
Fornisce un modo di accedere sequenzialmente agli oggetti presenti in una collezione, senza
mostrare la rappresentazione interna di questa.La collezione può essere implementata usando un
array, una linked list o qualunque altra struttura.
Una applicazione potrebbe essere interessata ad accedere agli elementi di questa collezione in una
sequenza particolare, ma senza dover interagire direttamente con il tipo di struttura interna.
Aggregate
Iterator
createIterator(): Iterator
first()
next()
isDone()
currentItem()
ConcreteAggregate
createIterator(): Iterator
- concreteAggregate
0..1
return new ConcreteIterator(this)
Viene usato il factory method nel AbstractList
ConcreteIterator
Esempio
Il percorso di un viaggiatore è rappresentato come una collezione ordinata di oggetti, dove ogni
oggetto rappresenta un luogo visitato.
import java.util.LinkedList;
import java.util.ListIterator;//importo la lista degli iteratori
public class IteratorExample {
public static void main(String[] args) {
List tour = new LinkedList();//creo la lista del tour
tour.add( "Santiago" );
tour.add( "Buenos Aires" );
tour.add( "Atlanta" );
tour.add( "New York" );
tour.add( "Madrid" );
tour.add( "Torino" );
tour.add( "Napoli" );
ListIterator travel = tour.listIterator();//creo la lista degli iteratori basandomi sulla
lista tour
System.out.println( "Percorso andata" );
while( travel.hasNext() )//uso il met degli iterator per determinare la sequ ini/fine
System.out.println(" "+((String) travel.next()) );
System.out.println( "Percorso ritorno" );
while( travel.hasPrevious() )//uso il met degli iterator per determinare la sequ fin/iniz
System.out.println(" "+((String) travel.previous()) );
}
}
Esecuzione
Percorso andata
Santiago
Buenos Aires
Atlanta
New York
Madrid
Torino
Napoli
Percorso ritorno
Napoli
Torino
Madrid
New York
Atlanta
Buenos Aires
Santiago
un altro esempio:
...main()
Aggregate test A = new ConcreteAggregateA();
testA.append(new Integer(1));
testA.append(new Integer(10));
testA.append(new Integer(100));
Aggregate test B = new ConcreteAggregateB();
testB.append(new Integer(1));
testB.append(new Integer(10));
testB.append(new Integer(100));
testB.append(new Integer(1000));
Iterator iterA=testA.createIterator();//creazione dell'iteratore per test A
iterA.first();
while(iterA.isDone()){
Integer n =(Integer)iterA.CurrentItem();
iterA.next();
System.out.println("Valore"+n);
}
//creazione dell iteratore per testB
Iterator iterB=testB.createIterator();
iterB.first();
while(iterB.isDone()){
Integer n =(Integer)iterB.CurrentItem();
iterB.next();
System.out.println("Valore"+n);
}
public abstract class Aggregate{
private Vector elements=new Vector();//vettore
public abstract Iterator createIterator();
//inserisci nuovo elemento
public void append(Object o){
element.addelements(o);
}
public Object currentItem(int n){
return elements.elementA+(n);
}
public int count(){
return elements.size();
}
public class CocreteAggregate extends Aggregate{
public Iterator createIterator();//istanza dell'iteratore
{
return new ConcreteIteratorA(this)//this serve per iterare l'oggetto stesso
}
}
public class ConcreteIteratorA implemenst Iterator {
public ConcreteIteratorA(Aggregate obj){
this.obj=obj;
}
public void first(){//metto a zero lo state
state=0;
}
public void next(){//aumento di 1 lo state
if(isDone()){
state++;
}
public boolean isDone(){//serve per vedere se ci sono ancora oggetti da leggere
if(state>obj.count()-1)
return true;
return false;
}
public Object CurrentItem(){//restituisce l'oggetto contenuto nel vector
return obj.CurrentItem(state)}
Observer:
E' utilizzato per tenere sotto controllo lo stato di diversi oggetti.
Sostanzialmente il pattern si basa su uno o più oggetti, chiamati osservatori o listener, che vengono
registrati per gestire un evento che potrebbe essere generato dall'oggetto "osservato".
La struttura di questo pattern è formata da:
observer è semplicemente un'interfaccia che dichiara il metodo update; tale metodo prende come
argomento un subject, che utilizzerà per richiedergli informazioni sui dati.
// idea di implementazione di un observer
public interface Observer {
public void update( Subject subject ) ;
}
Il subject è una classe che implementa certi metodi che potranno essere riutilizzati dalle classi che
agiranno come subject (derivando da questa classe).
public class Subject {
protected Vector observers = new Vector() ;
public void addObserver( Observer o ) {//aggiungi osservatore
observers.addElement( o ) ;
}
public void removeObserver( Observer o ) {//rimuovi osservatore
observers.removeElement( o ) ;
}
public void notify() {
Enumeration e = observers.getElements() ;
while ( e.hasMoreElements() ) {
((Observer)e.nextElement()).update( this ) ;
}
}
}
N.B. Devono essere utilizzate due classi utili : java.util.Observer
java.utilObservable
Nella classe Observable la notyfy Observers:
public void notyfyObservers(Object arg){
synchonized (this){
if(!changed) return;
arrLocal=obs.toArray();
clearChages()
}
for(int i=arrLocal.Lenght-1;i>=0;i--)
((Observer)arrLocal[i].update(this.args));
}
//fine della funzione all'interno della libreria
Esempio:Prende l'input da tastiera e tratta ogni linea di ingresso come un evento.
Codice:
classe EventSource
package OBS;
import java.util.Observable;
//Observable is here
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class EventSource extends Observable implements Runnable
{
public void run()
{
try
{
final InputStreamReader isr = new InputStreamReader( System.in );
final BufferedReader br = new BufferedReader( isr );
while( true )
{
final String response = br.readLine();
setChanged();
notifyObservers( response );
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
Classe ResponseHandler
package OBS;
import java.util.Observable;
import java.util.Observer; /* this is Event Handler */
public class ResponseHandler implements Observer
{
private String resp;
public void update (Observable obj, Object arg) //aggiorno lo stato
{
if (arg instanceof String)
{
resp = (String) arg;
System.out.println("\nReceived Response: "+ resp );
}
}
}
Classe MyAPP
package OBS;
public class myapp
{
public static void main(String args[])
{
System.out.println("Enter Text >");
// create an event source - reads from stdin
final EventSource evSrc = new EventSource();
// create an observer
final ResponseHandler respHandler = new ResponseHandler();
// subscribe the observer to the event source
evSrc.addObserver( respHandler );
// starts the event thread
Thread thread = new Thread(evSrc);
thread.start();
}
}
Riassumendo avremo le seguenti classi:
Soggetto: Una classe che fornisce interfacce per registrare o rimuovere gli observer e che
implementa le seguenti funzioni:
Attach
Detach
Notify
Soggetto Concreto: Classe che fornisce lo stato dell'oggetto agli observer e che si occupa di
effettuare le notifiche chiamando la funzione notify nella classe padre (Soggetto).
Contiene la funzione:
GetState - che restituisce lo stato del soggetto.
Observer: Questa classe definisce un'interfaccia per tutti gli observers, per ricevere le notifiche dal
soggetto. È utilizzata come classe astratta per implementare i veri Observer, ossia i
ConcreteObserver.
Funzioni:
Update - una funzione astratta che deve essere implementata dagli observer.
ConcreteObserver:
Questa classe mantiene un riferimento al Subject (Concreto), per ricevere lo stato quando avviene
una notifica.
La funzione non astratta è Update, quando questa viene chiamata dal Soggetto, il ConcreteObserver
chiama la gestate sul soggetto per aggiornare l'informazione su di esso.
-Pattern Facade
Fornisce una interfaccia unificata per un insieme di interfacce di un sottosistema, rendendo più
facile l’uso di quest’ultimo,Ovvero nella programmazione ad oggetti indica un oggetto che
permette, attraverso un'interfaccia più semplice, l'accesso a sottosistemi che espongono interfacce
complesse e molto diverse tra loro, nonché a blocchi di codice complessi.
Il vantaggio di strutturare un sistema in sottosistemi aiuta a ridurre la complessità .
Un esempio per capire a cosa serve questo pattern è il seguente
seguente situazione in cui una classe Client, per realizzare una singola operazione deve accedere ad
alcune classi molto differenti tra loro quindi il client le deve conoscere e saper usare e questo rende
il lavoro molto complesso siccome dovremo accede a più classi per utilizzare i loro metodi
L'utilizzo del pattern façade (qui realizzato attraverso la classe Facade) permette di nascondere la
complessità dell'operazione, poiché in questo caso la classe Client chiama soltanto il metodo
metodoUnico per realizzare la stessa operazione.
Il vantaggio è ancora più evidente se questo pattern viene utilizzato in una libreria software, poiché
rende indipendente l'implementazione della classe Client dall'implementazione dei vari oggetti
Class1, Class2, etc.
Esempio:
Un applicativo Java abilitato ad accettare dati dalla console, deve gestire
l’interazione con classi di Input/Output
la conversione di tipi di dati.
Per esempio, se si vuole leggere un numero :
BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
String aString ="";
// La lettura può generare un’eccezione
try {
aString = br.readLine();
} catch( IOException e ) { }
Se si vuole avere il dato numerico, ad es. Integer si dovrà fare:
// Si converte il valore letto a int
Integer anInteger = new Integer( aString );
Ogni volta che si vogliono inserire dati di tipo numerico il programmatore deve interagire con le
seguenti classi:
BufferedReader
InputStreamReader
System
IOException
String, Integer, Double, Float, ecc.
Sarebbe auspicabile una forma di interazione semplificata e per questo si ricorre al pattern Facade
Facade
Sub-System
classes
public class ConsoleReader {
public static final boolean SECURE_MODE_ON = true;
public static final boolean SECURE_MODE_OFF = false;
BufferedReader br;
boolean secureMode;
public ConsoleReader() {
this( SECURE_MODE_ON );
}
public ConsoleReader(boolean secMod ) {
br = new BufferedReader( new InputStreamReader( System.in ) );
secureMode = secMod;
}
public String readString() {
try {
return br.readLine();
} catch (Exception e) {
if ( secureMode )
throw new ConsoleReaderException( e );
return ""; } }
FacadeExample
ConsoleReader
readString(): String
readInteger(): Integer
readDouble(): Double
public Integer readInteger() {
Integer theInteger = null ;
try {
theInteger = new Integer( br.readLine() );
} catch (Exception e) {
if ( secureMode )
throw new ConsoleReaderException( e );
theInteger = new Integer( 0 );
}
return theInteger;}
public Double readDouble() {
Double theDouble = null ;
try {
theDouble = new Double( br.readLine() );
} catch (Exception e) {
if ( secureMode )
throw new ConsoleReaderException( e );
theDouble = new Double( 0 );
}
return theDouble;
}}
//Esempio di Facade
public class FacadeExample {
public static void main( String[] arg ) {
ConsoleReader reader = new ConsoleReader(
ConsoleReader.SECURE_MODE_OFF );
System.out.println( "This is the ’Insecure mode’: " +
"the system can’t detect invalid inputs." );
System.out.println( "Enter a string : ");
String text = reader.readString();
System.out.println( "You wrote: " + text );
System.out.println( "Enter an Integer : ");
Integer oInteger = reader.readInteger();
System.out.println( "You wrote: " + oInteger );
System.out.println( "Enter a Double : ");
Double oDouble = reader.readDouble();
System.out.println( "You wrote: " + oDouble );
System.out.println( "This is the ’Secure mode’: " +
"the system raises an exception when it " +
"detects invalid inputs." );
reader = new ConsoleReader( );
System.out.println( "Enter a string : ");
text = reader.readString();
System.out.println( "You wrote: " + text );
System.out.println( "Enter an Integer : ");
oInteger = reader.readInteger();
System.out.println( "You wrote: " + oInteger );
System.out.println( "Enter a Double : ");
oDouble = reader.readDouble();
System.out.println( "You wrote: " + oDouble );
}
}
ESECUZIONE:
This is the ’Insecure mode’: the system can’t detect invalid inputs.
Enter a string :
ciao
You wrote: ciao
Enter an Integer :
x
You wrote: 0
Enter a Double :
3
You wrote: 3.0
This is the ’Secure mode’: the system raises an exception when it detects invalid inputs.
Enter a string :
ciao ciao
You wrote: ciao ciao
Enter an Integer :
3
You wrote: 3
Enter a Double :
x
facade.ConsoleReaderException: java.lang.NumberFormatException: For input string: "x"
at facade.ConsoleReader.readDouble(ConsoleReader.java:57)
at facade.FacadeExample.main(FacadeExample.java:39)
Quindi i Vantaggi sono:
Nasconde l’implementazione di un sottosistema ai clients, semplificando l’uso del sottosistema
Promuove l’accoppiamento debole tra sottosistema e clients. Questo ci permette di cambiare le
classi del sottosistema senza effetti sui clients
Riduce il numero di dipendenze di compilazione in sistemi molto grandi
Semplifica il porting tra piattaforme diverse, perché diventa meno probabile che la costruzione di
un sottosistema richieda la costruzione di altri sottosistemi
Non impedisce, ai clients sofisticati , l’accesso alle sottoclassi
Da notare che Facade non aggiunge nessuna funzionalità, ma semplifica soltanto le interfacce
Esempio: Magazzino
Prendiamo in considerazione un negozio con un magazzino ed un magazziniere.
Nel magazzino, ci sono un sacco di cose, ad esempio materiale da imballaggio, materie prime e
prodotti finiti.
Un client deve accedere a diversi prodotti ma non sa dove sono posizionati
CODICE:
public class Client{
public static void main(String [] args){
Magazziniere m= new Magazzieniere();//classe di facciata è il magazzino
MateriePrime mp =new MateriePrime();
}
public Interface Goods{...}
public class ProdottiFiniti implements Goods{...}
public class MateriePrime implements Goods{...}
public class Imballaggio implements Goods{...}
public Interface Store{
Goods getGoods();//metodo che restituisce un oggetto
}
public class ImballaggioStore implements Store{
public Goods getGoods(){
Imballaggio imb=new Imballaggio();
return Imb;}}
//Facade in questo caso rappresenta il magazziniere
public class Magazziniere {
public MateriePrime getMateriePrime(){
MateriePrimeStore store= newMateriePrimeStore() ;
MateriePrime mp =(MateriePrime) store.GetGoods();//il cast serve perchè tirona un valore
//generico derivatio dall'interfaccia
return mp;
}//Vanno creati altri metodi per imballaggio e ProdottiFiniti;
-Pattern Adapter :
Usato per riutilizzare del codice già scritto in precedenza ovvero addattare il vecchio codice con le
nuove interfaccie del sistema
Object Adapter dal punto di vista UML è una "ASSOCIAZIONE"
Nel class Adapter invece viene usata l'ereditarietà;
Quindi Converte l’interfaccia di una classenell’interfaccia che il client si aspetta.
Esempio:
Si vuole realizzare un applicazione che permetta di lavorare con gli oggetti geometrici
?
Polygon è l’interfaccia per la gestione dei nuovi oggetti grafici,peròSi vuole riutilizzare una vecchia
classe Rectangle
come riutilizzare la classe esistente tramite la nuova interfaccia, e senza modificare
l’implementazione originale?
L’Adapter pattern offre due soluzioni possibili:
Class Adapter: crea una classe (RectangleClassAdapter) che implementa l’interfaccia Polygon ed
estende la classe Rectangle . I metodi della sottoclasse creata mappano le operazioni richieste ai
metodi e attributi della classe base esistente.
Object Adapter: si crea una nuova classe (RectangleObjectAdapter) che implementa l’interfaccia
Polygon richiesta, e che possiede al suo interno un’istanza della classe da riutilizzare. Le operazioni
della nuova classe fanno invocazioni ai metodi dell’oggetto interno.
Quindi se si vuole usare il pattern Adapter:
-Se si vuole sfruttare una classe esistente , ma questa ha una interfaccia non corrispondende a
quella che bisogna utilizare
-Se si vuole creare una classe riusabile cooperante con altre classi che non necessariamente hanno
un’interfaccia compatibile
CODICE riferito alla soluzione con Class Adapter:
public interface Polygon {
public void define( float x0, float y0, float x1, float y1,
String color );
public float[] getCoordinates() ;
public float getSurface();
public void setId( String id );
public String getId( );
public String getColor();
}
public class RectangleClassAdapter extends Rectangle implements Polygon{
private String name = "NO NAME";
public void define( float x0, float y0, float x1, float y1,
String color ) {
float a = x1 - x0;
float l = y1 - y0;
setShape( x0, y0, a, l, color );
}
public float getSurface() { return getArea(); }
public float[] getCoordinates() {
float aux[] = new float[4];
aux[0] = getOriginX();
aux[1] = getOriginY();
aux[2] = getOppositeCornerX();
aux[3] = getOppositeCornerY();
return aux;
}
public void setId( String id ) { name = id; }
public String getId( ) { return name; }
}
public class ClassAdapterExample {
public static void main( String[] arg ) {
Polygon block = new RectangleClassAdapter();
block.setId( "Demo" );
block.define( 3 , 4 , 10, 20, "ROSSO" );
System.out.println( "L'Area di "+ block.getId() + " e' "+
block.getSurface() + ", ed e' " +
block.getColor() );
}
}
ESECUZIONE:
L'Area di Demo e' 112.0, ed e' ROSSO
N.B. Il facade richiede la defizione di una nuova interfaccia invece Adapter non richiede la
defizione di una nuova interfaccia ma riutilizza un interfaccia vecchia