The visitor pattern is a sort of mirror of the builder pattern. Again, our goal will be to separate the object structure from the writing of the XML document. Figure 1.8 illustrates the generic visitor pattern. The various components are as follows :
Warning Don't confuse the class Element with an XML element. In the visitor pattern, Element stands for an element in the data structure. Figure 1.8. Visitor pattern.
One of the remarkable aspects of this pattern is how a Visitor object recognizes a concrete element. It would have been possible to explicitly test the various options, such as in the following: if(element instanceof Catalog) visitCatalog((Catalog)element); else if(element instanceof VisualProduct) visitVisualProduct((VisualProduct)element); // and more However, this method is error prone. It is particularly easy to forget to update this list of tests when new classes are added to the structure. Instead, Element and Visitor use a two-step protocol to recognize each other. Element implements the accept() method, which takes a Visitor as a parameter. When an Element accepts a Visitor , it calls the appropriate visitConcreteElement() method, passing a reference to itself, to the Visitor object. Applying the Visitor PatternFigure 1.9 applies the visitor pattern to our object structure. It introduces two new interfaces and two new classes:
Figure 1.9. Applying the visitor pattern.
The CatalogElement interface is shown in Listing 1.11. It declares only one method: accept() . Listing 1.11 CatalogElement.javapackage com.psol.catalog; import java.io.IOException; public interface CatalogElement { public void accept(CatalogVisitor visitor) throws IOException; } The accept() method is implemented in CatalogElement 's descendants, such as the Catalog class (refer to Listing 1.1): public void accept(CatalogVisitor visitor) throws IOException { visitor.visitCatalog(this); } Listing 1.12 is the CatalogVisitor . It declares one method for each element in the object structure. Listing 1.12 CatalogVisitor.jarpackage com.psol.catalog; import java.io.IOException; public interface CatalogVisitor { public void visitCatalog(Catalog catalog) throws IOException; public void visitVisualProduct(VisualProduct product) throws IOException; public void visitTextualProduct(TextualProduct product) throws IOException; public void visitDescription(Description description) throws IOException; } XMLVisitor , as seen in Listing 1.13, is one implementation of CatalogVisitor that writes the XML document. For each object, it writes the corresponding XML code. If the object contains other objects, it calls their accept() method, which causes these objects to be written as well: public void visitTextualProduct(TextualProduct product) throws IOException { pw.print("<Product"); printAttribute("id",product.getId()); Boolean bool = new Boolean(product.isChecked()); printAttribute("checked",bool.toString()); pw.println('>'); printElement("Text",product.getText()); pw.println("<Descriptions>"); for(int i = 0;i < product.getSize();i++) product.descriptionAt(i).accept(this); pw.println("</Descriptions>"); pw.println("</Product>"); } Listing 1.13 XMLVisitor.javapackage com.psol.catalog; import java.io.*; public class XMLVisitor implements CatalogVisitor { protected PrintWriter pw; public XMLVisitor(PrintWriter pw) { this.pw = pw; } public void visitCatalog(Catalog catalog) throws IOException { pw.println("<?xml version='1.0'?>"); pw.println("<Catalog>"); for(int i = 0;i < catalog.getSize();i++) catalog.productAt(i).accept(this); pw.print("</Catalog>"); pw.flush(); } public void visitVisualProduct(VisualProduct product) throws IOException { pw.print("<Product"); printAttribute("id",product.getId()); Boolean bool = new Boolean(product.isChecked()); printAttribute("checked",bool.toString()); pw.println('>'); printElement("Text",product.getText()); printElement("Image",product.getImage()); pw.println("</Product>"); } public void visitTextualProduct(TextualProduct product) throws IOException { pw.print("<Product"); printAttribute("id",product.getId()); Boolean bool = new Boolean(product.isChecked()); printAttribute("checked",bool.toString()); pw.println('>'); printElement("Text",product.getText()); pw.println("<Descriptions>"); for(int i = 0;i < product.getSize();i++) product.descriptionAt(i).accept(this); pw.println("</Descriptions>"); pw.println("</Product>"); } public void visitDescription(Description description) throws IOException { pw.print("<Text"); printAttribute("xml:lang",description.getLanguage()); pw.print('>'); printContent(description.getText()); pw.println("</Text>"); } public void printElement(String tag,String content) throws IOException { pw.print('<'); pw.print(tag); pw.print('>'); printContent(content); pw.print("</"); pw.print(tag); pw.println('>'); } public void printContent(String content) throws IOException { // works with any Writer encoding but EBCDIC for(int i = 0;i < content.length();i++) { char c = content.charAt(i); if(c == '<') pw.print("<"); else if(c == '&') pw.print("&"); else if(c > '\ u007f') { pw.print("&#"); pw.print(Integer.toString(c)); pw.print(';'); } else pw.print(c); } } public void printAttribute(String name,String value) throws IOException { pw.print(''); pw.print(name); pw.print("='"); // works with any Writer encoding but EBCDIC for(int i = 0;i < value.length();i++) { char c = value.charAt(i); if(c == '\ '') pw.print("'"); else if(c == '&') pw.print("&"); else if(c > '\ u007f') { pw.print("&#"); pw.print(Integer.toString(c)); pw.print(';'); } else pw.print(c); } pw.print('\ ''); } } The application's main class is shown in Listing 1.14, CatalogViewer . CatalogViewer creates a frame on which it places an instance of CatalogPanel (refer to Listing 1.3). At startup, it uses the builder pattern to read the catalog.xml file. When the window is closed, it uses the visitor pattern to overwrite catalog.xml . This saves any changes by the customer, such as selecting or deselecting a product: Writer writer = new FileWriter("catalog.xml"); CatalogVisitor visitor = new XMLVisitor(new PrintWriter(writer)); catalog.accept(visitor); Tip It would not be difficult to use JavaMail (the standard Java API for emailing) in conjunction with the visitor pattern to automatically email the product selection. This is left as an exercise for the reader. Listing 1.14 CatalogViewer.javapackage com.psol.catalog; import java.io.*; import java.awt.*; import org.xml.sax.*; import java.awt.event.*; import org.xml.sax.helpers.XMLReaderFactory; public class CatalogViewer { public static final String PARSER_NAME = "org.apache.xerces.parsers.SAXParser"; protected static class SaveOnClose extends WindowAdapter { protected Catalog catalog; public SaveOnClose(Catalog catalog) { this.catalog = catalog; } public void windowClosing(WindowEvent evt) { try { Writer writer = new FileWriter("catalog.xml"); CatalogVisitor visitor = new XMLVisitor(new PrintWriter(writer)); catalog.accept(visitor); } catch(IOException e) { } System.exit(0); } } public static void main(String[] args) throws Exception { XMLReader xmlReader = XMLReaderFactory.createXMLReader(PARSER_NAME); CatalogBuilder builder = new DefaultCatalogBuilder(); xmlReader.setContentHandler(new XMLDirector(builder)); xmlReader.parse("catalog.xml"); Catalog catalog = builder.getCatalog(); Panel panel = new CatalogPanel(catalog); Frame frame = new Frame("Catalog Viewer"); frame.add(panel); frame.setResizable(false); frame.setSize(400,200); frame.addWindowListener(new SaveOnClose(catalog)); frame.show(); } } |