Chapter 8. The Prototype Pattern

With the Prototype pattern, you can specify the general class needed in a program but defer specifying the exact class until execution time. It is similar to the Builder pattern in that some class decides what components or details make up the final class. However, it differs in that the target classes are constructed by cloning one or more prototype classes and then changing or filling in the details of the cloned class to behave as desired.

Prototypes can be used whenever you need classes that differ only in the type of processing that they offer, for example, when parsing strings that represent numbers in different radixes. In this sense, the Prototype is nearly the same as the Exemplar pattern described by Coplien [1992].

Let's consider the case in which you need to make a number of queries to an extensive database to construct an answer. Once you have this answer as a table or ResultSet, you might want to manipulate it to produce other answers without your having to issue additional queries.

For example, consider a database of a large number of swimmers in a league or statewide organization. Each swimmer swims several strokes and distances throughout a season. The best times for swimmers are tabulated by age group. Within a single four-month season, many swimmers will have birthdays and therefore move into new age groups. Thus the query to determine which swimmers did the best in their age groups that season depends on the date of each meet and on each swimmer's birthday. The computational cost of assembling this table of times is therefore fairly high.

Once you have a class containing this table, sorted by sex, you might want to examine this information sorted by time or by actual age rather than by age group. Recomputing this data isn't sensible, and you don't want to destroy the original data order, so some sort of copy of the data object is desirable.

Cloning in Java

You can make a copy of any Java object using the clone method.

JObj j1 = (JObj)j0.clone();

The clone method always returns an object declared to have a return type of Object. Thus you must cast it to the actual type of the object that you are cloning. Three other significant restrictions apply to this method:

  1. It is a protected method and can be called only from within the same class or a subclass.

  2. You can clone only objects that are declared to implement the Cloneable interface. All arrays are considered to implement the Cloneable interface.

  3. Any object whose class is Object does not implement Cloneable and throws the CloneNotSupported Exception.

Since the clone method is protected, this suggests packaging the public, visible clone method inside of the class, where it can access that protected clone method.

public abstract class SwimData implements Cloneable {

    public Object cloneMe() throws CloneNotSupportedException {
        return super.clone();
    }

You can also create special cloning procedures that change the data or processing methods in the cloned class, based on arguments that you pass to the clone method. In this case, method names such as make are probably more descriptive and suitable.

Using the Prototype

Now let's write a simple program that reads data from a database and then clones the resulting object. In our example program, SwimInfo, we just read these data from a file, but the original data were derived from a large database as we mentioned previously.

Then we create a class called Swimmer that holds one swimmer's name, age, sex, club, and time.

public class Swimmer {
    String name;    //name of swimmer
    int    age;     //age of swimmer
    String club;    //name of club
    float   time;       //result of time
    boolean female;     //sex

We also create a class called TimeSwimData (derived from SwimData) that maintains a Vector of the Swimmers we read in from the database.

public class TimeSwimData extends SwimData
implements Cloneable , Serializable {
    protected Vector swimmers;
 
    public TimeSwimData(String filename) {
        String s = "";
        swimmers    = new Vector();
        InputFile f = new InputFile(filename);
        s= f.readLine();
        while (s != null) {
            swimmers.addElement(new Swimmer(s));
            s= f.readLine();
        }
        f.close();
    }

We also provide a getSwimmer method in SwimData and getName, getAge, and getTime methods in the Swimmer class. Once we've read the data into SwimInfo, we can display it in a list box.

Then, when the user clicks on the Clone button, we'll clone this class and sort the data differently in the new class. Again, we clone the data because creating a new class instance would be much slower, and we want to keep the data in both forms.

public class SwimInfo extends Frame
    implements ActionListener {
    SwimData sdata;
    List swList, cloneList;
    Button Clone, Refresh, Quit;
    Swimmer sw;

    //-------------------------------
    private void cloneAndLoad() {
        SwimData sxdata = null;
        try{
            sxdata = (SwimData)sdata.cloneMe();
            sxdata.sort();
        }
        catch(CloneNotSupportedException e){}
 
        cloneList.removeAll();
        //now print out sorted values from clone
        for (int i = 0; i < sxdata.size(); i++) {
            sw = sxdata.getSwimmer(i);
            cloneList.add(sw.getName()+" "+sw.getTime());
        }
}

In the original class, the names are sorted by sex and then by time, while in the cloned class they are sorted only by time. In Figure 8.1, we see the simple user interface that allows us to display the original data on the left and the sorted data in the cloned class on the right.

Prototype example.

Figure 8.1. Prototype example.

The left-hand list box is loaded when the program starts, and the right-hand list box is loaded when you click on the Clone button. Now let's click on the Refresh button to reload the left-hand list box from the original data. The somewhat disconcerting result is shown in Figure 8.2.

Prototype example, after clicking on Clone and then on Refresh.

Figure 8.2. Prototype example, after clicking on Clone and then on Refresh.

Why have the names in the left-handed list box also been re-sorted? This occurs in Java because the clone method is a shallow copy of the original class. In other words, the references to the data objects are copies, but they refer to the same underlying data. Thus any operation performed on the copied data will also be done on the original data in the Prototype class.

In some cases, this shallow copy might be acceptable, but if you want to make a deep copy of the data, you must write a deep cloning routine of your own as part of the class you want to clone. In this simple class, you just create a new Vector using a constructor that creates the new instance and copies the elements of the old class's Vector into the new one.

public TimeSwimData(Vector sw) {
    //this constructor copies vector as a clone
    swimmers = new Vector();
    for (int i = 0; i < sw.size (); i++) {
        swimmers.add (sw.elementAt (i));
    }
}
//--------------
public SwimData myClone() {
    return new TimeSwimData(swimmers);
}

Using the Prototype Pattern

You can use the Prototype pattern whenever any of a number of classes might be created or when the classes are modified after being created. As long as all of the classes have the same interface, they can actually be from entirely different class hierarchies.

Let's consider a more elaborate example of the listing of swimmers we discussed previously. Instead of just sorting the swimmers, let's create subclasses that operate on the data, modifying it and presenting the result for display in a list box. We start with the abstract class SwimData, which has all abstract methods except for the deepClone method we just described.

//abstract class defining interface and deepClone routine
public abstract class SwimData
        implements Cloneable , Serializable {
public abstract int     size ();
public abstract Swimmer getSwimmer(int i);
public abstract String  getName(int i);
public abstract void    sort();
public abstract void    setFemale(boolean f);
public SwimData deepClone() {
    //as above
  }
}

Then it becomes possible to write different concrete SwimData classes depending on the application's requirements. We always start with the TimeSwimData class and then clone it for various other displays. For example, the SexSwimData class resorts the data by sex and displays only one sex, as shown in Figure 8.3.

The SexSwimData class displays only one sex, shown on the right.

Figure 8.3. The SexSwimData class displays only one sex, shown on the right.

In the SexSwimData class, we sort the data based on whether we want to display girls or boys. This class has the following additional method:

public void setFemale(boolean f) {
      female = f;
   }

Each time that you click on the button labeled Sex, the boolean representing whether to display females is complemented. Thus when you use the prototype to clone a copy of the base TimeSwimData class to make an instance of SexSwimData, we can also tell it which sex to display.

Classes do not even have to be that similar, however. The AgeSwimData class takes the cloned input data array and creates a simple histogram by age. If you have been displaying the boys, you see their age distribution, and if you have been displaying the girls, you see their age distribution, as shown in Figure 8.4.

The AgeSwimData class displays an age distribution.

Figure 8.4. The AgeSwimData class displays an age distribution.

In this case, the concrete class resulting from the prototype is two subclasses away from the base class; the first subclass selects a single sex, and the second subclass creates a histogram. In other words, the Prototype pattern effectively hides the concrete products from the client, in this case the display list. The same getName and size methods are used as in the superclasses, but here the displayed data are quite different. As long as the concrete product classes have the same interface, the Prototype is a suitable pattern choice. We have defined a different concrete class (female or male swimmer list) by creating a class and setting parameters within that class.

The UML diagram in Figure 8.5 illustrates this system fairly clearly. The SwimInfo class is the main GUI class. It keeps two instances of SwimData but does not specify which ones. The TimeSwimData and SexSwimData classes are concrete classes derived from the abstract SwimData class. The AgeSwimData class, which creates the histograms, is derived from the SexSwimData class.

The UML diagram for the various SwimData classes.

Figure 8.5. The UML diagram for the various SwimData classes.

You should also note that you are not limited to the few subclasses demonstrated here. You could easily create additional concrete classes and register them with whatever code selects the appropriate concrete class. In our previous example program, the user is the deciding point or factory, because he simply clicks on one of several buttons. In a more elaborate case, each concrete class could have an array of characteristics, and the decision point could be a class registry or prototype manager, which examines these characteristics and selects the most suitable class. In Java, you can load additional classes dynamically at runtime and make them available through this registry.

You could also combine the Factory Method pattern with the Prototype pattern to have each of several concrete classes use a concrete class different from those available.

Prototype Managers

A prototype manager class can be used to decide which of several concrete classes to return to the client. It can also manage several sets of prototypes at once. For example, in addition to returning one of several classes of swimmers, it could return different groups of swimmers who swam different strokes and distances. It could also manage which list boxes (of several types) are returned and to display them, including tables, multicolumn lists, and graphical displays. It is important that whichever subclass is returned does not require casting to a new class type in order to be used in the program. In other words, the methods of the parent abstract or base class should be sufficient, and the client should never need to know which actual subclass it is dealing with.

Cloning Using Serialization

Here is a clever trick, which some have called a "hack," for cloning using the serializable interface. A class is said to be serializable if you can write it out as a stream of bytes and then read those bytes back in to reconstruct the class. This is how Java remote method invocation (RMI) and Java Beans are implemented.

However, if we declare both the Swimmer and SwimData classes as Serializable,

public class SwimData
    implements Cloneable, Serializable

class Swimmer implements Serializable

we can write the bytes to an output stream and reread them to create a complete data copy of that instance of a class.

public Object deepClone() {
    try{
        ByteArrayOutputStream b = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(b);
        out.writeObject(this);
        ByteArrayInputStream bIn = new
               ByteArrayInputStream(b.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bIn);
        return (oi.readObject());
    }
    catch (Exception e) {
        System.out.println("exception:"+e.getMessage());
        return null;
    }
}

This deepClone method allows you to copy an instance of a class of any complexity and have data be completely independent between the two copies, as long as all of the classes that class contains can be declared serializable.

Consequences of the Prototype Pattern

Using the Prototype pattern,

  1. You can add and remove classes at runtime by cloning them as needed.

  2. You can revise the internal data representation of a class at runtime based on program conditions.

  3. You can also specify new objects at runtime without creating a proliferation of classes and inheritance structures.

One difficulty in implementing the Prototype pattern in Java is that if the classes already exist, you might not be able to change them to add the required clone or deepClone methods. Using the deepClone method can be particularly difficult if all of the class objects contained in a class cannot be declared serializable. In addition, classes that have circular references to other classes cannot be cloned.

Similar to Singletons, discussed in Chapter 6, you can create a registry of Prototype classes that can be cloned and then ask the registry object for a list of possible prototypes. You might be able to clone an existing class rather than writing one from scratch. Note, however, that every class that you might use as a prototype must itself be instantiated (perhaps at some expense) in order for you to use a Prototype registry. This can be a performance drawback.

Finally, the idea of having Prototype classes to copy implies that you have sufficient access to the data or methods in these classes to change them after cloning. This might require adding data access methods to these prototype classes so that you can modify the data once you have cloned the class.

Thought Question

  1. An entertaining banner program shows a slogan starting at different places on the screen at different times and in different fonts and sizes. Design the program using a Prototype pattern.

Programs on the CD-ROM

Program Description

PrototypeMyCloneSwimInfo.java

Clones using the constructor to create a new instance by copying the Vector.

PrototypeDeepProtoSwimInfo.java

Clones using serialization.

PrototypeSimpleProtoSwimInfo.java

Example of shallow cloning and prototype.

PrototypeAgeProtoSwimInfo.java

Shows the prototype used to display swimmers by time and sex, and which also gives the age distinction.

Summary of Creational Patterns

Following is a summary of the Creational patterns:

  • Factory pattern. used to choose and return an instance of a class from a number of similar classes based on data you provide to the factory.

  • Abstract Factory pattern. returns one of several groups of classes. In some cases, it actually returns a Factory pattern for that group of classes.

  • Builder pattern. assembles a number of objects to make a new object, based on the data with which it is presented. Often the choice of which way the objects are assembled is achieved using a Factory pattern.

  • Prototype pattern. copies or clones an existing class rather than creating a new instance, when creating new instances is more expensive.

  • Singleton pattern. ensures that there is one and only one instance of an object and that it is possible to obtain global access to that one instance.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset