Back in my C++ days, when I wrote CAD (computer aided drafting) programs, one of the more trying tasks was to write an API, a program that allowed third parties to offer symbol libraries of their products. Supposedly, they would sell more products if the clients used them in their drawings.
Unfortunately, the API ended up being cumbersome and difficult to use; in fact, I was the only one who used it to implement libraries. If I had known how to develop symbol libraries with the Prototype pattern, the projects would have been much simpler and more successful.
This tutorial builds upon Mark Grand’s work. In his article, he describes the Prototype design pattern and Java-related issues like cloning. I will be presenting an implementation of the pattern that builds on his work.
The Prototype Pattern
Start with an interface for each object. As shown in Listing 1, the interface is used to extend the Cloneable and Serializable interfaces. The Prototype pattern requires the ability to be cloned. Serialization is needed to implement objects in files.
Listing 1. CadSymbolIF.java
// CadSymbolIF - symbol library interface import java.awt.*; import java.io.*; public interface CadSymbolIF extends Cloneable, Serializable { // public String getName(); public void setName(String name); public String getType(); // some example method needed by the cad program public Dimension getExtents (); public void setExtents (Dimension d); public Dimension getOrigin (); public void setOrigin (Dimension d); public void draw (Graphics g); } |
Serialization
In order for the symbols to be read from a file, or stream, they must support serialization. Simply stated, object serialization provides a program with the ability to read or write objects from a stream. In our example, this stream comes from a file and a filter, in the form of ObjectInputStream(). ObjectInputStream is applied to the stream to convert it into a series of objects.
Any object that needs to be serialized must implement the Serializable interface. Since this interface has no methods, making something serializable is as simple as adding the “implements Serializable” clause to the class definition.
If there are special requirements, say, the objects require special storage, an object could alternately implement the Externalizable interface. The interface has two methods, readExternal() and writeExternal(). Both of these methods give the developer complete control over the serialization and re-constitution process; the developer can choose to encrypt the objects, for example, or, perform error-checking and/or compression.
Interface
The interface in Listing 1 contains definitions of some basic methods required by the CAD program. For example, every object will need to know where it is and how to draw itself. You may add as many methods as you need.
The actual objects
The CadSymbolIF interface is implemented by abstract class CadSymbol (see Listing 2), the base object. This class will contain the code common to all the CAD symbols.
Listing 2. CadSymbol.java
// CadSymbol - abstract base class import java.awt.*; import java.io.*; public abstract class CadSymbol implements CadSymbolIF { // methods for Cloneable interface public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { // This should never happen because this class implements Cloneable throw new InternalError(); } } // methods for Serializable interface, if needed // private void writeObject(java.io.ObjectOutputStream out) throws IOException // private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException; // identity methods private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } abstract public String getType(); // some example method needed by the cad program protected Dimension extents, origin; public Dimension getExtents () { return extents; } public void setExtents (Dimension d) { extents = d; } public Dimension getOrigin () { return origin; } public void setOrigin (Dimension d) { origin = d; } abstract public void draw (Graphics g); } |
An example symbol is presented in Listing 3. The getType() method has been implemented with a string unique to this symbol.
Listing 3. BasicUnit.java
// BasicUnit - an example of CadSymbol import java.awt.*; public class BasicUnit extends CadSymbol { public void draw (Graphics g) { // draw self } public String getType() { return "BasicUnit"; } } |
Loading the symbols
The CAD symbols are made available to the application in a serialized form in a file. The file must to be read into the application and the objects within it must be reconstituted. The CadSymbolManager, shown in Listing 4, stores the prototype objects in a vector using the type identifier of the object as the key. Supplying its identifier to the Manager via the getSymbol() method will allow it to retrieve an object.
Listing 4. CadSymbolManager.java
// CadSymbolManager import java.util.*; public class CadSymbolManager { private Vector symbols = new Vector(); // add symbol to collection void addSymbol(CadSymbol sym) { symbols.put(sym.getType(), sym); } // retrieve symbol by type CadSymbol getSymbol (String symbolType) { return (CadSymbol) ((CadSymbol)symbols.get(symbolType)).clone(); } } |
Listing 5. CadSymbolLoader.java
// CadSymbolLoader import java.util.*; public class CadSymbolLoader { private CadSymbolManager mgr; // constructor CadSymbolLoader (CadSymbolManager csm) { mgr = csm; } // load symbol libary int loadCadSymbols (String fname) { int symbolCount = 0; try { InputStream in; in = new FileInputStream(fname); in = new BufferedInputStream(in); ObjectInputStream oIn = new ObjectInputStream(in); while (true) { // loop until end of file Object c = oIn.readObject(); if (c instanceOf CadSymbol) { mgr.addSymbol((CadSymbol)c); symbolCount++; } } } catch (Exception e) { // do nothing } return symbolCount; } } |
Graphical Palettes
Graphical symbol palettes (little windows with clickable images in them), can be implemented by adding a getPaletteIcon() function to the CadSymbolIF interface. The function returns a small icon (.bmp, .gif, or whatever) representative of the symbol. Adding an iterator to the CadSymbolManager allows the CAD program to determine the icon and type of every symbol in a library. These symbols can then be used to construct the palette.
When the user clicks on a particular icon in the palette, the associated type will retrieve a symbol from the CadSymbolManager using the getSymbol() method.
Dynamic libraries
Using ObjectOutputStream to serialize the objects opens up some interesting possibilities for dynamic libraries. Instead of being read from the same static file every time, libraries could be supplied on a CD-ROM that, with a subscription, is updated periodically by the product distributor. The libraries could also be read directly from a manufacturer’s Web site during program startup (via RMI, for example), allowing for highly customized and timely components.
Properties
To make symbol libraries easy to locate and access, a properties file can be used to store the file name and location information. If the file is read from a Web site, the corresponding URL would be stored here. Any additional information, like a title for the palette, can also be stored here. The properties files can be used to implement multiple symbol libraries. A code to process multiple files would begin something like this:
Listing 6. Example
CadSymbolManager csm = new CadSymbolManager(); CadSymbolLoader csl = new CadSymbolLoader(csm); while ( /* read fname from properties */ ) { csl.loadCadSymbols(fname); } |
Conclusion
The use of an established design pattern makes implementation much easier for the developer. Instead of coming up with a solution on your own, it is reassuring to have a solution that has been discussed, proofed, and streamlined. If I had known about the Prototype pattern 10 years ago, my job would have been much simpler.
About the Author
With over thirteen years’ experience in application development, Wiebe de Jong is a Web developer for IMRglobal Ltd. in Vancouver, BC, Canada. He develops Internet and intranet applications for clients using Java, UML, and XML. He also teaches.