Scaricare la presentazione
La presentazione è in caricamento. Aspetta per favore
PubblicatoGianmarco Corradini Modificato 6 anni fa
1
Design patterns in pillole: factory method pattern
Andrea Franceschini Analista e (poco) programmatore Meeting del 18/06/2008
2
Cosa sono i design pattern
Soluzioni collaudate I design pattern formalizzano soluzioni comprovate a problemi ricorrenti nello sviluppo del software. Codificano e descrivono in maniera formale l'esperienza di programmatori nella risoluzione di problemi. Nascono dall'esperienza non sono l'applicazione di teorie o concetti, ma il risultato dell'esperienza di programmatori fatta “sul campo” e resa disponibile a tutti tramite una forma di catalogazione. Riusabilità sono massimamente riutilizzabili, perchè svincolati da una specifica tecnologia e applicabili allo stesso problema in un determinato contesto.
3
I pattern creazionali Scopo: astrarre la creazione di un oggetto
separano il codice che gestisce la creazione di un oggetto da quello che ne fa direttamente uso, introducendo un livello di astrazione. Benefici: Disaccoppiamento dalla classe concreta usata e riduzione della dipendenza tra codice client e codice dell'oggetto. Flessibilità di utilizzo che deriva dalla riduzione della dipendenza e la centralizzazione della parte di creazione dell'oggetto, che può essere riutilizzato altrove. Complessità dell' oggetto nascosta: la centralizzazione consente di gestire in modo trasparente al client la complessità dell'oggetto.
4
Factory method VS Abstract factory
Sono due dei principali pattern creazionali catalogati nella prima e basilare pubblicazione a riguardo “Design patterns: Elements of reusable object-oriented software” della GoF. Factory method: è class based, ovvero delega alle classi derivate la costruzione dell'oggetto richiesto. E' la classe derivata a determinare (non decidere) l'oggetto concreto creato. Abstract factory: è object based, ovvero delega ad un altro oggetto la costruzione dell'oggetto richiesto. Per via del disaccoppiamento che introducono nella creazione di oggetti entrambi si trovano spesso nella costruzione di framework e in ambienti distribuiti. Fanno propria la regola “program to an interface, not an object”.
5
definizione Scopo Aka Motivazione Applicabilità
Definire una interfaccia per la creazione di oggetti concreti ma laciare le sottoclassi decidere quale classe istanziare Aka Virtual constructor Motivazione Un framework spesso utilizza classi astratte per definire e mantenere relazioni tra oggetti, ed è responsabile della loro creazione Applicabilità Una classe non conosce in anticipo quale oggetto dovrà istanziare Una classe vuole delegare a sue versioni specializzate l'oggetto concreto da istanziare
6
Factory method Struttura architetturale pattern
Product: interfaccia implementata dall'oggetto creato ConcreteProduct: oggetto creato creator: interfaccia che definisce il factory method e restituisce un product concreteCreator: sottoclasse che ridefinisce il factory method
7
Un esempio Esempio evolutivo
Facciamo un esempio di factory pattern partendo da una situazione di progettazione non ottimale e vedendo come il pattern interviene a modificare l'architettura della applicazione. L'esempio si basa su un caso concreto: abbiamo una applicazione che legge dei dati contenenti letture di servizi di fornitura acqua e gas, acquisendoli da dei file di testo che possono avere diversi formati. La classe di acquisizione decide quale formato utilizzare per leggere i file in base al loro tipo, che viene passato come parametro. Per ogni formato di file abbiamo un “reader” dedicato, implementato da una classe, specifico per il formato del file ed il tracciato.
8
partenza Scenario: acquisizione di file in diversi formati
public class AcquisizioneLetture { public AcquisizioneLetture() { } public void parseFile(String fileName, String dataType) { FileLettureReader fileLettureReader; //questa è una interfaccia Lettura lettura; //questa è una interfaccia if (dataType.equals("gas")) { fileLettureReader = new GasLettureReader(fileName); } if (dataType.equals("H2O")) { fileLettureReader = new H2OLettureReader(fileName); } while (fileLettureReader.hasNextLettura()) { lettura = fileLettureReader.getNextLettura(); if (lettura.verifica()) { lettura.calcolaconsumo(); lettura.registra(); } else { lettura.scarta(); } } } } GasLettureReader, H2OLettureReader: classi specializzate FileLettureReader: interfaccia Lettura: interfaccia
9
Adeguamento richieste cliente
Su esigenza del cliente introduciamo le letture EE public class AcquisizioneLetture { public AcquisizioneLetture() { } public void parseFile(String fileName, String dataType) { FileLettureReader fileLettureReader; //questa è una interfaccia Lettura lettura; //questa è una interfaccia if (dataType.equals('gas')) { fileLettureReader = new GasLettureReader(fileName); } if (dataType.equals('H2O')) { fileLettureReader = new H2OLettureReader(fileName); } if(dataType.equals('EE')){ fileLettureReader = new EELettureReader(fileName); //la modifica introdotta per il cliente } while (fileLettureReader.hasNextLettura()) { lettura = fileLettureReader.getNextLettura(); if (lettura.verifica()) { lettura.calcolaconsumo(); lettura.registra(); } else { lettura.scarta(); } } } }
10
fileLettureReader = new GasLettureReader(fileName);
Problema e soluzione La classe non è chiusa alle modifiche Ogni volta che dovremo aggiungere un “reader” dovremo modificare la classe AcquisizioneLetture() per adeguarla alla nuova situazione. Inoltre la classe è fortemente accoppiata con i “reader”: fileLettureReader = new GasLettureReader(fileName); Isolare il codice che varia Nella nostra classe AcquisizioneLetture(), possiamo separare la parte di codice soggetta a modifiche da quella che non lo è, rendola quindi “chiusa alle modifiche”. Sicuramente la parte che legge le letture e le elabora non sarà soggetta a modifiche. Quello che varia è la parte di istanziazione dei “reader”. Incapsulare la creazione dei “reader” Creiamo una nuova classe ReaderFactory() in cui gestire la creazione e le modifiche ai “reader”
11
SimpleFactory public class ReaderFactory { private FileLettureReader fileLettureReader; public ReaderFactory() { } public FileLettureReader getFileLettureReader(String fileName, String dataType) { if (dataType.equals("gas")) { fileLettureReader = new GasLettureReader(fileName); } if (dataType.equals("H2O")) { fileLettureReader = new H2OLettureReader(fileName); } if (dataType.equals("EE")) { fileLettureReader = new EELettureReader(fileName); } return fileLettureReader; } } public class AcquisizioneLetture { private ReaderFactory factory; public AcquisizioneLetture(ReaderFactory factory) { this.factory = factory; } public void parseFile(String fileName, String dataType) { FileLettureReader fileLettureReader; //questa è una interfaccia Lettura lettura; //questa è una interfaccia fileLettureReader = factory.getFileLettureReader(fileName,dataType); while (fileLettureReader.hasNextLettura()) { lettura = fileLettureReader.getNextLettura(); if (lettura.verifica()) { lettura.calcolaconsumo(); lettura.registra(); } else { lettura.scarta(); } } } }
12
Risultato SimpleFactory Vantaggi:
abbiamo incapsulato la creazione e tutte le eventuali modifiche all'interno di una classe ReaderFactory() Vantaggi: AcquisizioneLetture() chiusa alle modifiche riutilizzabile anche altrove nel programma Disaccoppiare AcquisizioneLetture() dalle classi concrete dei “reader” Concentrare le modifiche in un punto preciso Questo costrutto non è un pattern, ma la semplice applicazione del principio di separare ciò che varia da ciò che non varia. Si incontra spesso anche nella variante “statica”. Dopo la modifica, questa è la struttura delle classi ottenuta:
13
Riepilogo classi
14
Il business cresce (anche i problemi)
Acquisizione clienti Acquisiamo 2 clienti importanti che vendono acqua e gas interessati al nostro sistema, ma dobbiamo adeguare il nostro programma ai formati XML dei loro file di interscambio. L'importanza della commessa esclude qualsiasi esitazione ed adeguiamo rapidamente il nostro codice alle nuove esigenze: Creiamo 2 nuove factory class ad hoc, derivate da ReaderFactory(): Cliente1readerFactory() Cliente2ReaderFactory() Le nuove classi ridefiniscono getFileLettureReader()per poter utilizzare 2 nuove classi “reader” per I formati XML e I tracciati
15
Il nuovo codice Factory per il cliente 1
public class Cliente1ReaderFactory extends ReaderFactory{ public Cliente1ReaderFactory() { } public FileLettureReader getFileLettureReader(String fileName, String dataType){ if (dataType.equals("gas")) { fileLettureReader = new Cliente1GasLettureReader(fileName); } if (dataType.equals("H2O")) { fileLettureReader = new Cliente1H2OLettureReader(fileName); } return fileLettureReader; } } Factory per il cliente 2 public class Cliente2ReaderFactory extends ReaderFactory { public Cliente2ReaderFactory() { } public FileLettureReader getFileLettureReader(String fileName, String dataType) { if (dataType.equals("gas")) { fileLettureReader = new Cliente2GasLettureReader(fileName); } if (dataType.equals("H2O")) { fileLettureReader = new Cliente2H2OLettureReader(fileName); } return fileLettureReader; } }
16
L'eterna lotta del bene contro il meglio
Approccio corretto il factory da utilizzare (quindi cosa e come leggere i files) è deciso fuori da AcquisizioneLetture() possiamo aggiungere futuri factory in modo trasparente al programma di acquisizione Il disaccoppiamento tra le classi coinvolte garantisce flessibilità Considerazioni AcquisizioneLetture() ha sempre bisogno che gli venga passato un factory nel costruttore Le operazioni sulla lettura sono sempre le stesse Domanda Possiamo rendere AcquisizioneLetture() autosufficiente e allo stsso tempo centralizzare tutta la gestione in un framework senza perdere la flessibilità ottenuta fino adesso? Il processo di per se è atomico.
17
protected abstract getFileLettureReader()
Ritorno alle radici Torniamo alla prima versione La prima vestione della classe AcquisizioneLetture() conteneva all'interno la creazione diretta dei “reader” utilizzati per leggere i file. Partendo da questa, modifichiamola incapsulando la creazione dei reader all'interno di un suo metodo astratto: protected abstract getFileLettureReader() quindi deriviamo dalla classe astratta ottenuta una classe ad hoc per ogni cliente, ognuno con la sua versione di getFileLettureReader() ma con tutte le altre funzioni che rimangono condivise tra le sottoclassi. Il metodo astratto ci fornisce la flessibilità di implementazione dei “reder” che avevamo ottenuto con i simple factory senza usare classi “esterne”, la parte di codice invariante rimane condivisa tra tutte le versioni di AcquisizioneLetture(). Questi metodi non conoscono quale “reader” utilizzano per leggere il file, perchè sarà determinato dalla particolare sottoclasse istanziata.
18
La classe astratta public abstract class AcquisizioneLetture { public AcquisizioneLetture() { } public void parseFile(String fileName, String dataType) FileLettureReader fileLettureReader; //questa è una interfaccia Lettura lettura; //questa è una interfaccia fileLettureReader =getFileLettureReader(fileName,dataType); while (fileLettureReader.hasNextLettura()) { lettura = fileLettureReader.getNextLettura(); if (lettura.verifica()) { lettura.calcolaconsumo(); lettura.registra(); } else { lettura.scarta(); } } } protected abstract FileLettureReader getFileLettureReader(String fileName, String fileType); }
19
Le classi derivate public class AcquisizioneLettureConcreta extends AcquisizioneLetture { public AcquisizioneLettureConcreta() { } protected FileLettureReader getFileLettureReader(String fileName, String fileType) { FileLettureReader fileLettureReader; if (fileType.equals("gas")) { fileLettureReader = new GasLettureReader(fileName); } if (fileType.equals("H2O")) { fileLettureReader = new H2OLettureReader(fileName); } if (fileType.equals("EE")) { fileLettureReader = new EELettureReader(fileName); } return fileLettureReader; } } public class AcquisizioneLettureCliente1 extends AcquisizioneLetture { public AcquisizioneLettureCliente1() { } protected FileLettureReader getFileLettureReader(String fileName, String fileType) { FileLettureReader fileLettureReader; if (fileType.equals("gas")) { fileLettureReader = new Cliente1GasLettureReader(fileName); } if (fileType.equals("H2O")) { fileLettureReader = new Cliente1H2OLettureReader(fileName); } return fileLettureReader; } }
20
Dove è il pattern Creator = AcquisizioneLetture()
Product = FileLettureReader() ConcreteCreator = AcquisizioneLettureConcreta(), AcquisizioneLettureCliente1(), AcquisizioneLettureCliente2() ConcreteProduct =GasLettureReader(), H2OLettureReader(), FactoryMethod = getFileLettureReader()
21
Diagramma UML architettura
22
Alcune note conclusive
Flessibilità nella applicazione: Le classi non sono perfettamente aderenti a quanto definito nel diagramma UML canonico, infatti il nostro factory method può restituire più di un product, ed è parametrico. Questo non toglie nulla alla bontà della soluzione Fondamentali: Interfacce Ereditarietà Polimorfismo disaccoppiamento
Presentazioni simili
© 2024 SlidePlayer.it Inc.
All rights reserved.