Il 0% ha trovato utile questo documento (0 voti)
2 visualizzazioni70 pagine

15 Design Pattern II

Il documento discute i design pattern, in particolare i pattern creazionali come il Factory Method e l'Abstract Factory, evidenziando come questi pattern aiutino a separare la logica di creazione degli oggetti dalla loro utilizzo. Viene fornito un esempio pratico con un sistema di ordinazione di pizze e un gioco di labirinti, dimostrando come l'uso di factory possa migliorare la flessibilità e la manutenibilità del codice. Inoltre, si sottolineano i vantaggi e gli svantaggi di ciascun pattern, nonché l'importanza di seguire i principi di programmazione orientata agli oggetti.

Caricato da

Moji Bake
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
2 visualizzazioni70 pagine

15 Design Pattern II

Il documento discute i design pattern, in particolare i pattern creazionali come il Factory Method e l'Abstract Factory, evidenziando come questi pattern aiutino a separare la logica di creazione degli oggetti dalla loro utilizzo. Viene fornito un esempio pratico con un sistema di ordinazione di pizze e un gioco di labirinti, dimostrando come l'uso di factory possa migliorare la flessibilità e la manutenibilità del codice. Inoltre, si sottolineano i vantaggi e gli svantaggi di ciascun pattern, nonché l'importanza di seguire i principi di programmazione orientata agli oggetti.

Caricato da

Moji Bake
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 70

15.

Design pattern II
IS 2024-2025

Laura Semini, Jacopo Soldani


Corso di Laurea in Informatica
Dipartimento di Informatica, Università of Pisa
REMINDER

«Each pattern describes a problem which occurs over and over again in our environment, and
then describes the core of the solution to that problem, in such a way that you can use this
solution a million times over, without ever doing it the same way twice»
(Christopher Alexander, A Pattern Language, 1977)

Pattern per ogni fase del processo software

Analysis

Design ← design pattern 

Implementation

Maintenance
2
(SIMPLE)
FACTORY

3
FACTORY: CI SERVONO?
Si, nel laboratorio di reti

Ma come si progettano?

4
PATTERN CREAZIONALI
I pattern creazionali astraggono il processo di istanziazione degli oggetti
• Nascondono l’effettiva creazione degli oggetti
• Rendono il sistema indipendente da come i suoi oggetti sono creati e composti
• Semplificano la costruzione di oggetti complessi (rispetto alla chiamata esplicita dei costruttori)

Una classe factory ha il solo compito di creare e restituire istanze di altre classi

Creare un oggetto mediante una classe «factory» riduce linee di codice e complessità,
consentendo di creare/inizializzare rapidamente oggetti

Esempi (in Java):


• sequenze di tasti (KeyStroke)
• connessioni di rete (SocketFactory)
• log (LogFactory)
5
OSSERVAZIONI SU «NEW»
Invocando il comando new per creare un oggetto, violiamo il principio «code to an interface»
• Assegniamo a una variabile (con tipo interfaccia) un oggetto ottenuto da una classe concreta
• Esempio: List list = new ArrayList()

Inoltre, consideriamo una classe che controlla alcune variabili e istanzia una particolare classe
basata su tali variabili, p.e.
if (condition) { return new ArrayList(); }
else { return new LinkedList();}
La classe in questione:
• dipende da ogni classe riferita al suo interno
• deve essere aggiornata e ricompilata se cambiano le classi riferite Questo si può
(aggiunta di nuove classi, rimozione di classi esistenti) evitare con le factory
⇒ Violazione dei principi «open-closed» e «information hiding»

6
TRE TIPI DI FACTORY

• Simple Factory (detto anche ConcreteFactory)


• non è un pattern GoF
• è una semplificazione molto diffusa di Abstract Factory

• Factory Method

• Abstract Factory

7
SIMPLE FACTORY (AKA. CONCRETE FACTORY)
Problema: Chi deve creare gli oggetti quando la logica di creazione è complessa e si vuole
separare la logica di creazione dalle altre funzionalità di un oggetto?

Soluzione: Delega a un oggetto factory che gestisce la creazione

8
ESEMPIO
public class PizzaStore { // ---------------------------------
Pizza orderPizza(String type){ // Stays the same, independent from
Pizza pizza; // the actual pizza type
// ---------------------------------
if (type == "cheese") //
pizza = new CheesePizza(); // This is the creation code
else if (type == "pepperoni") // - dependent on the pizza classes
pizza = new PepperoniPizza(); // - more complex as we add pizzas
else if (type == "pesto") //
pizza = new PestoPizza(); //
// ---------------------------------
pizza.prepare(); // This is the preparation code
pizza.bake(); // - stays the same
pizza.package(); // - independent from pizza type
return pizza //
} // ---------------------------------
}

9
ESEMPIO (CONT.)

Estrarre il creation code e metterlo in una


classe che si occupi solamente di creare
pizze (PizzaSimpleFactory)
10
ESEMPIO (CONT.) public class PizzaSimpleFactory {
public Pizza createPizza(String type) {
public class PizzaStore { if (type == "cheese")
return new CheesePizza();
private PizzaFactory pf; else if (type == "pepperoni")
return new PepperoniPizza();
PizzaStore(PizzaSimpleFactory pf) { else if (type == "pesto")
this.pf = pf return new PestoPizza();
} }
}
Pizza orderPizza(String type){
Pizza pizza = this.pf.createPizza(type) • Istanziazione concreta rimpiazzata da
una chiamata a factory
pizza.prepare(); • Non è necessario toccare PizzaStore
pizza.bake(); se aggiungiamo nuove pizze!
pizza.package();
return pizza
}
}

11
ESEMPIO (CONT.)
Ok, ma possiamo fare meglio in termini di
flessibilità, utilizzando gli altri due design pattern:
factory method e abstract factory

12
TRE TIPI DI FACTORY

• Simple Factory (detto anche ConcreteFactory)


• non è un pattern GoF
• è una semplificazione molto diffusa di Abstract Factory

• Factory Method
• è un «class creational pattern»
• usa l’ereditarietà per decidere l’oggetto da istanziare

• Abstract Factory
• è un «object creational pattern»
• usa il meccanismo di delega (delegation) per istanziare altri oggetti

13
FACTORY METHOD

14
IL FACTORY METHOD PATTERN

Delega alle sottoclassi la decisione di quali classi istanziare

NB: Creator è scritto senza sapere quali siano i prodotti che saranno effettivamente creati
 scelta dovuta all’effettiva sottoclasse (di Creator) che viene instanziata

15
IL FACTORY METHOD PATTERN (CONT.)
Creator dichiara il
Product definisce l’interfaccia factory method
per il tipo di oggetti che sono (usato da altri metodi)
creati dal factory method

ConcreteProduct
implementa
l’interfaccia di Product ConcreteCreator sovrascrive il
factory method per restituire
un’istanza del ConcreteProduct

16
ESEMPIO

Consideriamo la seguente evoluzione del PizzaStore


• Differenti franchise per parti diverse del paese (es. California, New York, Chicago)
• Ogni franchise ha la propria factory per creare pizze in linea con i gusti dei clienti locali
• Mantenere il processo di preparazione (comune) che determina il successo di PizzaStore

Con il factory method, possiamo rendere PizzaStore una superclasse astratta


• «code to an interface» nella superclasse // processo di preparazione comune
• creazione degli oggetti nelle sottoclassi // pizze adatte ai gusti dei locali

17
ESEMPIO (CONT.)
public abstract class PizzaStore { Creator
protected abstract createPizza(String type);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake(); public class NYPizzaStore extends PizzaStore {
pizza.cut(); public Pizza createPizza(String type) {
pizza.box(); if(type.equals("cheese")) return new NYCheesePizza();
return pizza; if(type.equals("greek")) return new NYGreekPizza();
} if(type.equals("pepperoni")) return new NYPepperoniPizza();
} return null;
}
} ConcreteCreator
18
UN ALTRO ESEMPIO: MAZE
Vogliamo costruire un labirinto con stanze, pareti e porte

19
MAZE (CONT.)
public class MazeGame { r2.setSide(MazeGame.North, new Wall());
// Create the maze r2.setSide(MazeGame.East, new Wall());
public Maze createMaze() { r2.setSide(MazeGame.South, new Wall());
Maze maze = new Maze(); r2.setSide(MazeGame.West, door);
Room r1 = new Room(1); return maze;
Room r2 = new Room(2); }
Door door = new Door(r1, r2); }
maze.addRoom(r1);
maze.addRoom(r2);
r1.setSide(MazeGame.North, new Wall());
r1.setSide(MazeGame.East, door);
r1.setSide(MazeGame.South, new Wall());
r1.setSide(MazeGame.West, new Wall());
createMaze crea le componenti e le
// continues on next column assembla (ha due responsabilità)

20
MAZE (CONT.)

Il problema di createMaze è la sua inflexibility

• Come fare se vogliamo un labirinto magico con EnchantedRoom e EnchantedDoor? E se


vogliamo elementi nascosti come DoorWithLock e WallWithHiddenDoor?

• Sarebbero necessari cambiamenti significativi a createMaze a causa delle istanziazioni


esplicite (con new) per la creazione delle componenti del labirinto

• Come riprogettare il tutto in modo da rendere «facile» la creazione di nuovi tipi di componenti
con createMaze?

21
MAZE (CONT.)
public class MazeGame {
// Factory methods
public Maze makeMaze() {
return new Maze();
} L’implementazione concreta del labirinto
public Room makeRoom(int n) { è delegata alle sottoclassi
return new Room(n);
I metodi make* possono essere
}
• astratti o
public Wall makeWall() { • concreti (implementazione di default)
return new Wall();
}
public Door makeDoor(Room r1, Room r2) {
return new Door(r1, r2);
}
...
22
MAZE (CONT.)
...
public Maze createMaze() {
Maze maze = makeMaze();
Room r1 = makeRoom(1);
Room r2 = makeRoom(2);
Door door = makeDoor(r1, r2);
maze.addRoom(r1);
maze.addRoom(r2);
r1.setSide(MazeGame.North, makeWall());
r1.setSide(MazeGame.East, door);
...
r2.setSide(MazeGame.West, door);
return maze;
}
}
23
MAZE (CONT.)
Abbiamo reso createMaze un po’ più complessa, ma molto più flessibile!

Creiamo un labirinto incantato?


public class EnchantedMazeGame extends MazeGame {
public Room makeRoom(int n) {
return new EnchantedRoom(n);
} Il metodo createMaze è ereditato da
MazeGame e può essere usato per creare
public Wall makeWall() {
altri labirinti senza modifiche!
return new EnchantedWall();
}
public Door makeDoor(Room r1, Room r2) {
return new EnchantedDoor(r1, r2);
}
}
24
MAZE: OSSERVAZIONI
Funziona perché createMaze delega la creazione dei componenti del labirinto alle sottoclassi

Nell’esempio:

• Creator ⟹ MazeGame

• ConcreteCreator ⟹ EnchantedMazeGame // ma anche MazeGame per la versione default

• Product ⟹ Wall, Room, Door

• ConcreteProduct ⟹ EnchantedWall, EnchantedRoom, EnchantedDoor

NB: Maze è un ConcreteProduct (ma anche un Product)

25
SUL FACTORY METHOD PATTERN
Utilizzo
• Consente di disaccoppiare una classe dalle classi degli oggetti che crea e utilizza
• Delega alle sottoclassi la specifica degli oggetti da creare
Benefici
• Il codice è reso più flessibile e riusabile (senza l’istanziazione delle classi application-specific)
• Code to an interface (la classe si basa sull’interfaccia di Product e può funzionare con
qualunque ConcreteProduct che supporti tale interfaccia)
Svantaggi
• Necessario estendere la classe Creator anche solo per istanziare un particolare
ConcreteProduct
Implementazione
• I Creator possono essere astratti o concreti (con metodi di default)
• Se usato per creare diversi tipi di prodotto, il factory method deve avere un parametro per
decidere quale tipo di oggetto creare (p.e., con if-then-else)

26
ABSTRACT FACTORY

27
L’ABSTRACT FACTORY PATTERN

Delega ad altre classi la decisione di quali classi istanziare

Definisce un’interfaccia
• per creare «famiglie di oggetti» (correlati e dipendenti fra loro)
• senza specificare le classi concrete

Come cambia l’istanziazione di oggetti tra Abstract Factory e Factory Method?


• Factory Method → Delegata a sottoclassi sfruttando l’ereditarietà Composizione?
• Abstract Factory → Delegata ad altri oggetti mediante composizione

NB: Spesso l’oggetto delegato usa factory method per effettuare


l’istanziazione, quindi si applicano entrambi i pattern

28
L’ABSTRACT FACTORY PATTERN (CONT.)

29
MAZE, WITH ABSTRACT FACTORY
// MazeFactory – Abstract Factory
public interface MazeFactory {
public Maze makeMaze();
public Room makeRoom(int n);
public Wall makeWall();
public Door makeDoor(Room r1, Room r2);
}
// BasicMazeFactory – Concrete factory for basic parts
public class BasicMazeFactory implements MazeFactory {
public Maze makeMaze() { return new BasicMaze(); }
public Room makeRoom(int n) { return new BasicRoom(n); }
public Wall makeWall() { return new BasicWall(); }
public Door makeDoor(Room r1, Room r2) { return new BasicDoor(r1, r2); }
}

30
MAZE, WITH ABSTRACT FACTORY (CONT.)
public class MazeGame {
// createMaze now inputs a MazeFactory reference as parameter
public Maze createMaze(MazeFactory factory) {
Maze maze = factory.makeMaze();
Room r1 = factory.makeRoom(1);
Room r2 = factory.makeRoom(2);
Door door = factory.makeDoor(r1, r2);
maze.addRoom(r1);
maze.addRoom(r2);
r1.setSide(MazeGame.North, factory.makeWall());
...
return maze; createMaze delega la responsabilità di
} creare le componenti del labirinto
} all’oggetto MazeFactory

31
MAZE, WITH ABSTRACT FACTORY (CONT.)
// Another concrete factory
public class EnchantedMazeFactory implements MazeFactory {
public Room makeRoom(int n) { return new EnchantedRoom(n); }
public Wall makeWall() {return new EnchantedWall(); }
public Door makeDoor(Room r1, Room r2) { return new EnchantedDoor(r1, r2); }
}

In questo esempio abbiamo:


• AbstractFactory → MazeFactory
• ConcreteFactory → BasicMazeFactory, EnchantedMazeFactory
• AbstractProduct → Wall, Room, Door
• ConcreteProduct → BasicWall, BasicRoom, BasicDoor, EnchantedWall, EnchantedRoom,
EnchantedDoor

32
MAZE: FACTORY METHOD VS ABSTRACT FACTORY
MazeGame

public Wall makeWall() { return new EnchantedWall(); }


33
E IL PIZZA STORE?
Grande successo del factory method per la realizzazione di franchise diversi, ma abbiamo
scoperto che alcuni franchise
• seguono le nostre procedure (forzatamente, grazie al codice astratto in PizzaStore)
• risparmiano sulla qualità degli ingredienti per ridurre i costi e aumentare gli utili

NO! QUESTO È
INACCETTABILE!
Il successo della nostra compagnia è dovuto anche
all’utilizzo di ingredienti freschi e di qualità

34
PIZZA STORE V2

Usiamo un abstract factory per reperire gli ingredienti utilizzati per preparare le pizze

• Regioni diverse usano ingredienti diversi ⇒ creiamo sottoclassi «region-specific» del factory

• Indipendentemente dai requisiti «region-specific», ci assicureremo che i factory forniscano


ingredienti in linea con gli standard di qualità di PizzaStore

(I franchise dovranno trovare un altro modo per ridurre i costi )

35
PIZZA STORE V2 (CONT.)
// Abstract factory for pizza ingredients
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce(); Richiede l’introduzione di nuove classi
public Cheese createCheese(); astratte come Dough, Sauce, Cheese, etc.
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClams();
}

36
PIZZA STORE V2 (CONT.)
// Concrete factory for Chicago pizzas
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() { return new ThickCrustDough(); }
public Sauce createSauce() { return new PlumTomatoSauche(); }
public Cheese createCheese() { return new MozzarellaCheese(); }
public Veggies[] createVeggies() {
Veggies veggies[] = { new BlackOlives(), new Spinach, new Eggplant() };
return veggies;
}
public Pepperoni createPepperoni() { return new SlicedPepperoni(); }
public Clams createClams() { return new FrozenClams(); } Ok, ma
}
dove
Consente di garantire la qualità degli ingredienti usati durante si usa?
la preparazione delle pizze, tenendo anche in considerazione
i gusti dei clienti di Chicago
37
PIZZA STORE V2 (CONT.)
// Pizza base class, now with an abstract "prepare" method
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Ora basta aggiornare le sottoclassi in
Veggies veggies[];
modo che utilizzino IngredientFactory
Cheese cheese;
Pepperoni pepperoni;
Clams clams;
abstract void prepare(); // now this is abstract!
void bake() { System.out.println("Bake for 25 minutes at 350"); }
void cut() { ... }
...

38
PIZZA STORE V2 (CONT.)
// A concrete (subclass of) Pizza
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingrFactory;
public CheesePizza(PizzaIngredientFactory ingrFactory) {
this.ingrFactory = ingrFactory;
Non ci servono più sottoclassi tipo
}
NYCheesePizza o ChicagoCheesePizza,
void prepare() { perché PizzaIngredientFactory tiene
dough = ingrFactory.createDough(); già conto delle differenze regionali
sauce = ingrFactory.createSauce();
cheese = ingrFactory.createCheese();
}
}
Dobbiamo però aggiornare i vari PizzaStore in modo che creino
le pizze usando PizzaIngredientFactory appropriati

39
PIZZA STORE V2 (CONT.)
// A concrete (subclass of) PizzaStore
public class ChicagoPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingrFactory = new ChicagoPizzaIngredientFactory();
if (item.equals("cheese")) {
pizza = new CheesePizza(ingrFactory);
pizza.setName("Chicago Style Cheese Pizza");
} else if (item.equals("veggie")) {
pizza = new VeggiePizza(ingrFactory);
pizza.setName("Chicago Style Veggie Pizza");ù
} else ...

40
PIZZA STORE V2: RECAP

• Abbiamo creato un abstract factory per gli ingredienti (aka. PizzaIngredientFactory)

• L’abstract factory fornisce un’interfaccia per la creazione di «famiglie di prodotti»

⇒ Disaccoppia il codice client dall’implementazione del factory che genera i prodotti concreti

• Consente al codice cliente (aka. PizzaStore e sottoclassi)


• Selezione e utilizzo del factory più appropriato alla regione
• Creazione del giusto stile di pizza (con il factory method)
• Utilizzo del giusto set di ingredienti (con abstract factory)

41
ESERCIZIO
Si consideri la produzione di due prodotti di due brand:
• Prodotti: TV e Remote Control (RC)
• Brand: Samsung e Philips
(NB: Una TV Samsung usa un Samsung RC, mentre una TV Philips usa un Philips RC)

Fornire quanto segue:


1. Implementazione con Simple Factory (unico factory con parametri)
2. Implementazione con Factory Method (un creator astratto costruito per avere una TV ed il suo
RC, che poi vengono impacchettati in un box)
3. Implementazione con Abstract Factory (un cliente sceglie quale factory usare e richiede la
creazione dei prodotti che servono, per poi impacchettarli in un box)

42
POSSIBILE SOLUZIONE: SIMPLE FACTORY

43
POSSIBILE SOLUZIONE: FACTORY METHOD

44
POSSIBILE SOLUZIONE: ABSTRACT FACTORY

45
PURE FABRICATION
Si tratta di un pattern GRASP che sostanzialmente assegna responsabilità fortemente correlate
ad una classe artificiale
• Implementa behavioural decomposition con l’obiettivo di supportare high cohesion, low
coupling e riuso
• La classe artificiale non rappresenta niente nel dominio del problema
• La classe artificiale è introdotta solo per convenienza del designer
(NB: Il nome pure fabrication deriva proprio da questi ultimi due punti)

In generale, un factory è un pure fabrication con l’obiettivo di


• Confinare la responsabilità di creazioni complesse in oggetti coesi
• Incapsulare la complessità della logica di creazione

46
SINGLETON

47
UNA FABBRICA DI CIOCCOLATO
Consideriamo una fabbrica di cioccolato, in cui un sistema informatico controlla la fase riguardante
i «chocolate boiler», il cui compito è quello di
• mescolare latte e cioccolato,
• metterli in un bollitore e
• passarli fase successiva (creazione delle barrette di cioccolato)

Una delle principali funzioni del sistema è quella di


prevenire incidenti, ad esempio
• disperdere 500 litri di prodotto non ancora bollito
• riempire il bollitore quando è già pieno
• accendere il bollitore quanto è vuoto

48
CONTROLLARE CHOCOLATE BOILER
I boiler sono controllati dalla classe ChocolateBoiler

{empty} fill() {!empty}

{!empty and boiled} drain() {empty}

{!empty and !boiled} boil() {!empty and boiled}

49
PROBLEMA

Il cioccolato è strabordato dal boiler! È stato


aggiunto altro latte anche se il boiler era già
pieno!

Come mai?
• Suggerimento: cosa succede se creiamo più
istanze di ChocolateBoiler?
• Due istanze che controllano lo stesso boiler
potrebbero avviare la procedura fill
contemporaneamente

50
IL SINGLETON PATTERN

Garantisce che una classe abbia una sola istanza


fornendo un punto di accesso globale a tale istanza

Attenzione
• La necessità di avere oggetti unici è abbastanza comune

• La maggior parte degli oggetti in un’applicazione ha un’unica responsabilità assegnata

• Le classi singleton però sono relativamente rare

 Il fatto che un oggetto/classe sia unico non significa che usi il singleton pattern

51
IL SINGLETON PATTERN (CONT.)

• Risponde alla necessità di avere una singola istanza di una classe in un sistema
(p.e. window manager, o un unico factory per un insieme di prodotti)

• Rende tale istanza facilmente accessibile

• Garantisce che non vengano create altre istanze

Come?

52
IL SINGLETON PATTERN (CONT.)
• Risponde alla necessità di avere una singola istanza di una classe in un sistema
• Rende tale istanza facilmente accessibile
• Garantisce che non vengano create altre istanze

1) Si rende il costruttore della classe privato


private ChocolateBoiler() = { ... }

2) Si aggiunge un oggetto statico privato (che conterrà l’unica istanza disponibile)


private static ChocolateBoiler _chocolateBoiler = new ChocolateBoiler()

3) Si rende l’unica istanza disponibile accessibile solo attraverso un metodo statico


public static ChocolateBoiler getChocolateBoiler() { return _chocolateBoiler; }

53
INIZIALIZZAZIONE LAZY
Invece di creare ogni oggetto singleton dall’inizio, meglio aspettare che sia necessario

private static ChocolateBoiler _chocolateBoiler = null;

public static ChocolateBoiler GetChocolateBoiler() {


if (_chocolateboiler == null) {
_chocolateboiler = new ChocolateBoiler();
}
return _chocolateboiler
}

54
INIZIALIZZAZIONE LAZY: PERCHÉ?
• Potremmo non avere informazioni sufficienti per istanziare il singleton staticamente
p.e. un ChocolateBoiler potrebbe dover attendere che siano stati stabiliti i canali di
comunicazione con gli altri macchinari della fabbrica di cioccolato

• Il singleton potrebbe essere «resource intensive» e non necessario


p.e. un programma che si connette ad un database ed esegue query opzionali

_chocolateBoiler == null _chocolateBoiler != null

55
IL SINGLETON PATTERN, IN UML

nel nostro esempio

Funzionerà anche in
ambienti multi-thread?

56
Cos’è successo?
Il codice del singleton torna
Sarà mica l’ottimizzazione multi-thread?

Si.

57
SINGLETON E MULTI-THREAD

Thread diversi potrebbero inizializzare più istanze del singleton (quasi) simultaneamente

Thread 1 Thread 2
public static ChocolateBoiler getInstance()
public static ChocolateBoiler getInstance()
if (_chocolateBoiler == null)
if (_chocolateBoiler == null)
_chocolateBoiler = new ChocolateBoiler()
_chocolateBoiler = new ChocolateBoiler()
return _chocolateBoiler
return _chocolateBoiler

58
SINGLETON E MULTI-THREAD
Thread diversi potrebbero inizializzare più istanze del singleton (quasi) simultaneamente

Possibili soluzioni:
• Inizializzare eager (invece di inizializzazione lazy)
 OK, ma alloca memoria anche per istanze che «non servono»

• Sincronizzazione del metodo «getInstance» (dichiarandolo synchronized)


 OK, ma può ridurre le performance del sistema

Come si fa?
• Double-checked locking
 Evita sincronizzazioni costose per tutte le invocazioni eccetto la prima

59
DOUBLE-CHECKED LOCKING
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() { ; }
public static Singleton getInstance() { Una variabile volatile garantisce che
if (uniqueInstance == null) { ogni thread acceda all’ultimo valore
assegnatole (quando la referenzia)
synchronized(Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton(); Solo un thread alla volta può
} eseguire un blocco di codice
} synchronized
}
return uniqueInstance; Servono due controlli sulla variabile
} uniqueInstance per garantire che sia
} ancora null

60
SINGLETON E SOTTOCLASSI
Cosa succede se estendiamo una classe singleton? Come possiamo consentire che l’unica
istanza sia istanza di una sottoclasse?

Ad esempio:
• MazeFactory ha due sottoclassi EnchantedMazeFactory and AgentMazeFactory
• Vogliamo istanziarne solo una
Come fare?
• Un metodo statico getInstance in MazeFactory che decide quale sottoclasse istanziare (es.
in base a parametri o variabili d’ambiente)

61
ESEMPIO
public abstract class MazeFactory {
// The private reference to the one and only instance
private static MazeFactory uniqueInstance = null;

// Create the instance using the specified String name


public static MazeFactory getInstance(String name) {
if(uniqueInstance == null) {
if(name.equals("enchanted")) I costruttori delle sottoclassi
uniqueInstance = new EnchantedMazeFactory(); devono essere pubblici per
else if(name.equals("agent")) consentire a MazeFactory di
istanziarle (ma anche altre
uniqueInstance = new AgentMazeFactory();
classi possono farlo)
}
return uniqueInstance; // this may not be the one specified by the parameter
}
}

62
ESEMPIO (CONT.)
Inoltre, getInstance viola il principio open-closed (richiede modifiche per nuove sottoclassi)

Possiamo però usare i nomi delle classi Java come argomenti del metodo getInstance
public static MazeFactory getInstance(String name) {
if (uniqueInstance == null)
// Usa la reflection per creare un'istanza della classe specificata da nome
uniqueInstance = Class.forName(name).newInstance();
return uniqueInstance;
}

Ma questo comunque non risolve il nostro problema

63
SINGLETON E SOTTOCLASSI
Cosa succede se estendiamo una classe singleton? Come possiamo consentire che l’unica
istanza sia istanza di una sottoclasse?

Ad esempio:
• MazeFactory ha due sottoclassi EnchantedMazeFactory and AgentMazeFactory
• Vogliamo istanziarne solo una
Come fare?
• Un metodo statico getInstance in MazeFactory che decide quale sottoclasse istanziare (es.
in base a parametri o variabili d’ambiente)
 I costruttori delle sottoclassi devono essere pubblici
 Altri clienti possono creare altre istanze delle sottoclassi 
• Ogni sottoclasse fornisce un metodo statico getInstance
I costrtuttori delle sottoclassi possono essere privati

64
ESEMPIO
public abstract class MazeFactory {
// The protected reference to the one and only instance
protected static MazeFactory uniqueInstance = null;
// The MazeFactory constructor (a default constructor cannot be private)
protected MazeFactory() {}
// Returns a reference to the single instance
public static MazeFactory getInstance() { return uniqueInstance; }
} public class EnchantedMazeFactory extends MazeFactory {
// Return a reference to the single instance.
public static MazeFactory getInstance() {
if(uniqueInstance == null) uniqueInstance = new EnchantedMazeFactory();
return uniqueInstance;
}
// Private subclass constructor
private EnchantedMazeFactory() {}
} 65
ESEMPIO (CONT.)

Codice per la creazione della factory (lato client)


MazeFactory factory = EnchantedMazeFactory.getInstance()

Codice per l’accesso alla factory (lato client)


MazeFactory factory = MazeFactory.getInstance()

Osservazioni
• Se un client accede alla factory prima che sia creata, ottiene null
• La uniqueInstance ora è protected
• I costruttori delle sottoclassi ora sono private
⇒ se ne può creare solo un’istanza

66
MEMENTO: ATTRIBUTI STATICI
(Ogni oggetto di una classe ha la propria copia di tutte le variabili di istanza di quella classe)

In alcuni casi, tutti gli oggetti di una classe devono condividere una sola copia di una variabile

⇒ Variabile statica

• Rappresenta informazioni relative all’intera classe

• Se ne tiene in memoria una sola copia (indipendentemente dal numero di oggetti)

• Accessibile attraverso il nome della classe e l'operatore punto (es. Math.PI)

• Accessibile solo attraverso i metodi e le proprietà della classe

67
SINGLETON VS. CLASSI STATICHE

Pro
• Un singleton può essere passato come parametro a un altro metodo
• Un singleton può essere esteso da una o più sottoclassi
• Un singleton può essere istanziato mediante fabric (scegliendo anche quale classe istanziare)

Cons
• Quelli appena visti
• https://www.oracle.com/technical-resources/articles/java/singleton.html

In entrambi i casi, attenzione al multi-threading!

68
Q
A

69
RIFERIMENTI
Contenuti
• Capitoli 16 e 17 di "Software Engineering" (G. C. Kung, 2023)

Approfondimenti

Potrebbero piacerti anche