15 Design Pattern II
15 Design Pattern II
Design pattern II
IS 2024-2025
«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)
Analysis
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
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
• 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?
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.)
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
• 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
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
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.)
• 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!
Nell’esempio:
• Creator ⟹ MazeGame
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
Definisce un’interfaccia
• per creare «famiglie di oggetti» (correlati e dipendenti fra loro)
• senza specificare le classi concrete
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); }
}
32
MAZE: FACTORY METHOD VS ABSTRACT FACTORY
MazeGame
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
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
⇒ Disaccoppia il codice client dall’implementazione del factory che genera i prodotti concreti
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)
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)
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)
48
CONTROLLARE CHOCOLATE BOILER
I boiler sono controllati dalla classe ChocolateBoiler
49
PROBLEMA
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
Attenzione
• La necessità di avere oggetti unici è abbastanza comune
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)
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
53
INIZIALIZZAZIONE LAZY
Invece di creare ogni oggetto singleton dall’inizio, meglio aspettare che sia necessario
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
55
IL SINGLETON PATTERN, IN UML
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»
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;
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;
}
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.)
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
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
68
Q
A
69
RIFERIMENTI
Contenuti
• Capitoli 16 e 17 di "Software Engineering" (G. C. Kung, 2023)
Approfondimenti