Applicabilità di Prototype
Questo pattern creazionale, proposto da GoF [1], si applica nelle situazioni in cui si vuole presentare al client un'interfaccia per la creazione di un insieme di oggetti, nascondendone le classi concrete. Notiamo subito che l'intento del pattern è identico a quello di Abstract Factory (AF) [2] e per questo motivo i due pattern entrano spesso in conflitto. Tuttavia, l'applicazione di Prototype si rende necessaria, e quindi alternativa ad AF, nei casi in cui:
● le classi sono specificate a run time; ● si vuole evitare la costruzione di factory parallele: si ricordi che in AF, diversamente, si creano tante factory quanti sono i ConcreteProduct che si intende realizzare; ● le istanze delle classi hanno un insieme ridotto di stati.
Il diagramma UML del pattern è quello di figura 1.
Vediamo nel dettaglio chi sono i partecipanti:
● Prototype (classe astratta): definisce il comportamento comune a tutte le classi che la useranno come esempio. Questa classe deve definire un metodo per fornire copie di sé stessa. ● ConcretePrototype (classe concreta): definisce la classe concreta (o le classi) che estenderanno il Prototype. ● Client (classe concreta): richiama il metodo di clonazione sui ConcretePrototype.
Il design originale di questo pattern [1] richiede che il Prototype definisca una operazione di clonazione per ottenere le nuove istanze delle classi. Tale operazione, in Java, è fornita dal metodo clone() della superclasse Object. Tuttavia, in alcuni testi come [3], viene sconsigliato il ricorso al metodo citato prima per evitare di ottenere oggetti con troppi attributi, e si consiglia piuttosto la creazione ex novo dell'oggetto con l'operatore new. Questo discorso è corretto ovviamente se la gerarchia di ereditarietà è molto profonda e, soprattutto, se si implementa il Prototype come classe concreta. Ma se riusciamo ad astrarre il concetto di Prototype, e quindi a renderlo una classe astratta al più sottoclasse di Object, possiamo sfruttare senza alcun problema la clonazione fornita nativamente dal linguaggio. In [1], viene consigliato di utilizzare una classe che funga da Prototype Manager per tener traccia dei ConcretePrototype, il cui numero potrebbe non essere fissato a tempo di compilazione. Utilizzeremo, nell'esempio che segue, proprio questo approccio.
Il lettore universale di documenti rivisitato
Riprendiamo l'esempio visto in [2]. Vogliamo aggiungere al nostro lettore universale di documenti due funzionalità:
● la possibilità di definire a run time il tipo di documento da leggere; ● la definizione a run time di un nuovo lettore, cioè di un nuovo ConcretePrototype, senza la necessità di definire una nuova factory.
Vediamo come si possono risolvere questi problemi applicando il pattern Prototype. Definiamo intanto il diagramma UML utilizzando il pattern:
Figura 2 - Applicazione del pattern all'esempio
Implementazione del lettore. Vediamo adesso l'implementazione della soluzione descritta nel paragrafo precedente. Iniziamo dalla classe DocumentPrototype:
package mb_prototype; public abstract class DocumentPrototype implements Cloneable { protected String type; protected String documentContent; public abstract void openDocument(String documentName); public String getType() {
return type;
} public String getDocumentContent() {
return documentContent;
} public DocumentPrototype cloneDocument() throws CloneNotSupportedException {
DocumentPrototype clone = (DocumentPrototype)super.clone();
return clone;
}
}
package mb_prototype;
import java.io.IOException;
import java.util.List;
import org.pdfbox.pdmodel.PDDocument;
import org.pdfbox.util.PDFTextStripper;
public class PDFDocument extends DocumentPrototype{
public PDFDocument() {
type = Constants.PDF_FILE;
}
public void openDocument(String documentName) {
String realName = documentName+"."+getType();
PDDocument document = null;
PDFTextStripper stripper = null;
try {
document = PDDocument.load(realName);
stripper = new PDFTextStripper();
documentContent = stripper.getText(document);
}
catch (IOException ex) {
ex.printStackTrace();
}
finally {
try {
document.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
In essa è definito un costruttore di default che inizializza il tipo di documento (attributo type) a PDF. Attraverso la libreria PDFBox [5], viene eseguita l'apertura e la lettura del documento, il cui contenuto è memorizzato nell'attributo documentContent, ereditato dalla classe base DocumentPrototype. DOCDocument e RTFDocument sono simili;
ciò che le differisce dalla classe precedente è ovviamente il metodo di apertura del documento:
package mb_prototype;
import java.io.FileInputStream;
import java.io.IOException;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
public class DOCDocument extends DocumentPrototype {
public DOCDocument() {
type = Constants.DOC_FILE;
}
public void openDocument(String documentName) {
String realName = documentName+"."+getType();
HWPFDocument document = null;
WordExtractor docContent = null;
FileInputStream fis = null;
try {
fis = new FileInputStream(realName);
document = new HWPFDocument(fis);
docContent = new WordExtractor(document);
documentContent = docContent.getText();
}
catch (Exception ex) {
ex.printStackTrace();
}
finally {
try {
fis.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
package mb_prototype; import java.io.FileInputStream;
import java.io.IOException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.rtf.RTFEditorKit; public class RTFDocument extends DocumentPrototype { public RTFDocument() {
type = Constants.RTF_FILE;
} public void openDocument(String documentName) {
String realName = documentName+"."+getType();
RTFEditorKit rtfReader = new RTFEditorKit();
DefaultStyledDocument document = new DefaultStyledDocument();
FileInputStream fis = null;
try {
fis = new FileInputStream(realName);
rtfReader.read(fis, document, 0);
documentContent = document.getText(0, document.getLength());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
package mb_prototype;
import java.util.HashMap;
import java.util.Map;
public class PrototypeManager {
private Map documentPrototypes;
// Migliorabile con Singleton
public PrototypeManager() {
initPrototypes();
}
private void initPrototypes() {
documentPrototypes = new HashMap();
documentPrototypes.put("pdf", new PDFDocument());
documentPrototypes.put("rtf", new RTFDocument());
}
public void addDocumentPrototype(String documentType, String documentReader) {
try {
// RTTI - No Reflection
Class c = Class.forName(documentReader);
DocumentPrototype dp = (DocumentPrototype)c.newInstance();
documentPrototypes.put(documentType, dp);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public DocumentPrototype getDocument(String documentType)
throws CloneNotSupportedException, DocumentNotSupportedException {
DocumentPrototype dp = null;
try {
dp = ((DocumentPrototype)documentPrototypes.get(documentType)).cloneDocument();
}
catch (NullPointerException npe) {
throw new DocumentNotSupportedException();
}
return dp;
}
}
package mb_prototype; public class DocumentNotSupportedException extends Exception { public DocumentNotSupportedException() {
super("Formato di documento non supportato!");
}
}
package mb_prototype;
import java.io.Console;
public class Client {
public static void main(String[] args) throws
CloneNotSupportedException, DocumentNotSupportedException {
String str;
String documentRealName;
String documentContent = new String();
String documentType = new String();
DocumentPrototype document;
PrototypeManager pm = new PrototypeManager();
String[] documentNameSplit = null;
do {
Console cons;
cons = System.console();
str = cons.readLine();
// F | nome documento.estensione
if (str.matches("F")) {
// legge il nome del file <nomefile>.<estensione>
String nomeFile = cons.readLine();
documentNameSplit = nomeFile.split("\\.");
document = pm.getDocument(documentNameSplit[1]);
document.openDocument(documentNameSplit[0]);
documentContent = document.getDocumentContent();
documentType = document.getType();
// N | estensione documento | classe reale if (str.matches("N")) {
// <estensione> String type = cons.readLine();
// <nomeclasse> String realClass = cons.readLine();
pm.addDocumentPrototype(type, realClass);
}
} while (!str.equals("E"));
}
}
http://www2.mokabyte.it/cms/section.run?name=mb123
