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.