3. Factory Method

Transcript

3. Factory Method
Factory Method 22
3. Factory Method
(GoF pag. 107)
3.1. Descrizione
Definisce un’interfaccia per creare oggetti, ma lascia alle sottoclassi la
decisione del tipo di classe a istanziare.
3.2. Esempio
Si pensi ad un framework per la manipolazione di elementi cartografici.
Due classi astratte sono fondamentali per il progetto di questo
framework: la classe Elemento che rappresenta qualunque tipo di
oggetto da posizionare in una mappa (es. luoghi e collegamenti), e la
classe Strumento, che fornisce le operazioni comuni di manipolazione
degli Elementi. Dato che entrambi classi sono astratte,
l’implementazione di un applicativo che sia in grado di gestire un
particolare tipo di mappa (che utilizza un insieme definito di Elementi),
richiede l’estensione di esse.
Tools: New Place New Conn.
Place:
Evian
(France)
Connector:
Lac Léman
Place:
Lausanne
(Switzerland)
Si fa notare che lo strumento, è in grado di stabilire quando un
particolare tipo di elemento deve essere creato (ad esempio, dopo di
aver richiesto un identificativo per un nuovo elemento), ma non il tipo
particolare di Elemento a creare. In altre parole, il framework deve
creare istanze di classi, ma soltanto è in grado di avere conoscenza
delle classi astratte, che non possono essere istanziate.
3.3. Soluzione
Il pattern “Factory Method” suggerisce il portare via dal framework la
creazione di ogni particolare tipo di Elemento. Per fare ciò, verrà
delegato alle sottoclassi dello Strumento, che specializzano le funzioni di
gestione di ogni tipo di Elemento, il compito di creare le particolari
istanze di classi che siano necessarie.
Factory Method 23
3.4. Struttura del pattern
<<stereotype>>
<<stereotype>>
anOperation{
…
product = factoryMethod();
…
}
Creator
Product
#factoryMethod(): Product
+anOperation()
<<stereotype>>
ConcreteProduct
<<stereotype>>
ConcreteCreator
creates
<<instantiates>>
#factoryMethod(): Product
3.5. Applicazione del pattern
Schema del modello
ElementHandler
<<interface>>
MapElement
#newElement(): MapElement
+createElement(): MapElement
+paintElement()
+setLabel()
+getPaintingData(): String
PlaceHandler
#newElement(): MapElement
Place
Connector
+setLabel()
+getPaintingData(): String
+setLabel()
+getPaintingData():String
creates
ConnectorHandler
#newElement(): MapElement
+connect( conn: Connector,
origin: Place,
destination: Place )
creates
Partecipanti
Œ
Product: classe astratta MapElement.
-
Œ
ConcreteProduct: classi Place e Connector
-
Œ
Definisce l’interfaccia di tutti gli elementi da utilizzare
nell’applicazione.
Implementano i concreti prodotti.
Creator: classe ElementHandler.
Factory Method 24
Œ
Dichiara il factory method (metodo newElement) che
restituisce un oggetto della classe Product.
Richiama il factory method per creare i Product.
ConcreteCreator: classi PlaceHandler e ConnectorHandler
-
Redefine il factory method per restituire una istanza di
ConcreteProduct.
Descrizione del codice
L’interfaccia MapElement fornisce la definizione dei servizi comuni a
tutti i Product da gestire nell’applicazione, che nel caso di questo
diventano due: Place (luoghi) e Connector (collegamenti fra luoghi). A
livello di framework, insieme al MapElement esiste l’ElementHandler,
che fornisce la procedura di creazione e gestione di questi oggetti.
L’ElementHandler si specializza in un PlaceHandler e in un
ConnectorHandler, che hanno il compito di istanziare il particolare
tipo di MapElement, quando sia necessario.
Si presenta adesso il codice delle classi appartenenti al framework, vale
dire, l’interfaccia MapElement: e la classe astratta ElementHandler:
public interface MapElement {
public abstract void setLabel( String id );
public abstract String getPaintingData();
}
import java.io.*;
public abstract class ElementHandler {
public MapElement createElement( ) throws IOException {
BufferedReader reader = new BufferedReader( new InputStreamReader(
System.in) );
System.out.println( "Enter a label for the element: ");
String label = reader.readLine();
MapElement element = newElement( );
element.setLabel( label );
return element;
}
public abstract MapElement newElement();
public void paintElement(MapElement element) {
System.out.println( element.getPaintingData() );
}
}
Si noti che il metodo createElement fornisce il codice necessario per
creare qualunque tipo di Product (MapElement). L’istanziazione del
particolare elemento è delegata al metodo newElement, il quale è
dichiarato astratto.
Quando viene sviluppata l’applicazione cartografica a partire di questo
framework, si implementano le classi concrete per la rappresentazione
degli elementi della mappa e per la loro gestione, vale dire i
ConcreteProduct e i ConcreteCreator.
Factory Method 25
Nel caso dell’esempio gli elementi della mappa (ConcreteProduct)
corrispondono alle classi Place (per i luoghi) e Connector (per i
collegamenti):
class Place implements MapElement {
private String placeLabel;
public void setLabel( String label ) {
placeLabel = label;
}
public String getPaintingData() {
return "city: "+ placeLabel;
}
}
class Connector implements MapElement {
private String connectorLabel;
Place place1, place2;
public void setLabel( String label ) {
connectorLabel = label;
}
public void setPlacesConnected( Place origin, Place destination ) {
place1 = origin;
place2 = destination;
}
public String getPaintingData() {
return connectorLabel + " [from " +
place1.getPaintingData() + " to " +
place2.getPaintingData() + "]";
}
}
Uno dei ConcreteCreator corrisponde alla classe PlaceHandler, che
implementa la creazione di un oggetto Place nella chiamata al metodo
newElement.
public class PlaceHandler extends ElementHandler {
public MapElement newElement() {
return new Place();
}
}
Allo stesso modo, l’altro ConcreteCreator, progettato per la gestione dei
Connector, corrisponde alla classe ConnectorHandler, che anche
estende l’ElementHandler, e implementa il metodo newElement, per
creare oggetti della classe Connector.
public class ConnectorHandler extends ElementHandler {
public MapElement newElement() {
return new Connector();
}
public void connect(Connector conn, Place origin, Place destination) {
conn.setPlacesConnected( origin, destination );
}
}
Factory Method 26
Si
è
inclusa
una
funzionalità
aggiuntiva
nella
classe
ConnectorHandler, che serve a connettere due luoghi (metodo
connect.
Finalmente si presenta il codice dell’applicazione che dimostra il
funzionamento di questo pattern:
import java.io.*;
public class FactoryMethodExample {
public static void main (String[] arg) throws IOException {
// Creates the tools for handling elements
ConnectorHandler cTool = new ConnectorHandler();
PlaceHandler pTool = new PlaceHandler();
// Vars
Place startPoint, endPoint;
Connector route;
// Creates two places and one connector
System.out.println( "1st. place creation" );
startPoint = (Place) pTool.createElement();
System.out.println( "2nd. place creation" );
endPoint
= (Place) pTool.createElement();
System.out.println( "Connector creation" );
route
= (Connector) cTool.createElement();
// Links places with the connection
cTool.connect( route, startPoint , endPoint );
// Paints the entire map
pTool.paintElement( startPoint );
pTool.paintElement( endPoint );
cTool.paintElement( route );
}
}
Osservazioni sull’esempio
In questo esempio la gerarchia di creatori (ElementHandler –
PlaceHandler – ConnectorHandler) rispecchia la gerarchia di
prodotti (MapElement – Place - Connector), ma questo non è un
requisito strutturale imposto dal pattern: è perfettamente possibile
trovarsi davanti a prodotti condivisi tra due o più ConcreteCreator.
Esecuzione dell’esempio
C:\Design Patterns\Creational\Factory Method>java FactoryMethodExample
1st. place creation
Enter a label for the element:
Evian
2nd. place creation
Enter a label for the element:
Lausanne
Connector creation
Enter a label for the element:
Lac Léman
city: Evian
city: Lausanne
Lac Léman [from city: Evian to city: Lausanne]
Factory Method 27
3.6. Osservazioni sull’implementazione in Java
Si vuole rendere noto che il factory method (newElement) dichiara
come tipo da restituire al punto di chiamata, sia nel Creator
(ElementHandler), sia in ogni ConcreteCreator (PlaceHandler e
ConnectorHandler), un oggetto di tipo Product (MapElement), invece
dei particolari tipi da produrre (Place e Connector). Questo è dovuto
al fatto che le sottoclassi che redefiniscono un metodo devono
esplicitare lo stesso tipo di ritorno che quello indicato nella dichiarazione
del metodo nella superclasse.