A problem with the design above is that it still requires a central location where all the types of the objects must be known: inside the factory( ) method. If new types are regularly being added to the system, the factory( ) method must be changed for each new type. When you discover something like this, it is useful to try to go one step further and move all of the information about the type-including its creation-into the class representing that type. This way, the only thing you need to do to add a new type to the system is to inherit a single class.
To move the information concerning type creation into each specific type of Trash, the “prototype” pattern (from the Design Patterns book) will be used. The general idea is that you have a master sequence of objects, one of each type you’re interested in making. The objects in this sequence are usedonly for making new objects, using an operation that’s not unlike the clone( ) scheme built into Java’s root class Object. In this case, we’ll name the cloning method tClone( ). When you’re ready to make a new object, presumably you have some sort of information that establishes the type of object you want to create, then you move through the master sequence comparing your information with whatever appropriate information is in the prototype objects in the master sequence. When you find one that matches your needs, you clone it.
In this scheme there is no hard-coded information for creation. Each object knows how to expose appropriate information and how to clone itself. Thus, the factory( ) method doesn’t need to be changed when a new type is added to the system.
One approach to the problem of prototyping is to add a number of methods to support the creation of new objects. However, in Java 1.1 there’s already support for creating new objects if you have a reference to the Class object. With Java 1.1 reflection (introduced in Thinking in Java) you can call a constructor even if you have only a reference to the Class object. This is the perfect solution for the prototyping problem.
The list of prototypes will be represented indirectly by a list of references to all the Class objects you want to create. In addition, if the prototyping fails, the factory( ) method will assume that it’s because a particular Class object wasn’t in the list, and it will attempt to load it. By loading the prototypes dynamically like this, the Trash class doesn’t need to know what types it is working with, so it doesn’t need any modifications when you add new types. This allows it to be easily reused throughout the rest of the chapter:
# PatternRefactoring/trash/Trash.py # Base class for Trash recycling examples. class Trash: def __init__(self, wt): self.weight = wt def getValue(self): pass def getWeight(self): return weight # Sums the value of Trash given an # Iterator to any container of Trash: def sumValue(self, it): val = 0.0f while(it.hasNext()): # One kind of RTTI: # A dynamically-checked cast Trash t = (Trash)it.next() val += t.getWeight() * t.getValue() print ( "weight of " + # Using RTTI to get type # information about the class: t.getClass().getName() + " = " + t.getWeight()) print("Total value = " + val) # Remainder of class provides # support for prototyping: trashTypes = ArrayList() def factory(self, info): for i in trashTypes: # Somehow determine the type # to create, and create one: tc = trashTypes.get(i) if (tc.getName().index(info.id) != -1): try: # Get the dynamic constructor method # that takes a double argument: ctor = tc.getConstructor(type(double)) # Call the constructor # to create a object: return (Trash)ctor.newInstance() except ex: ex.printStackTrace(System.err) raise "Cannot Create Trash" # Class was not in the list. Try to load it, # but it must be in your class path! try: print("Loading " + info.id) trashTypes.add(Class.forName(info.id)) except e: e.printStackTrace(System.err) raise "Prototype not found" # Loaded successfully. # Recursive call should work: return factory(info) class Messenger: def __init__(self, name, val): self.id = name self.data = val The basic **Trash** class and **sumValue( )** remain as before. The rest of the class supports the prototyping pattern. You first see two inner classes (which are made **static**, so they are inner classes only for code organization purposes) describing exceptions that can occur. This is followed by an **ArrayList** called **trashTypes**, which is used to hold the **Class** references.
In Trash.factory( ), the String inside the Messenger object id (a different version of the Messenger class than that of the prior discussion) contains the type name of the Trash to be created; this String is compared to the Class names in the list. If there’s a match, then that’s the object to create. Of course, there are many ways to determine what object you want to make. This one is used so that information read in from a file can be turned into objects.
Once you’ve discovered which kind of Trash to create, then the reflection methods come into play. The getConstructor( ) method takes an argument that’s an array of Class references. This array represents the arguments, in their proper order, for the constructor that you’re looking for. Here, the array is dynamically created using the Java 1.1 array-creation syntax:
This code assumes that every Trash type has a constructor that takes a double (and notice that double.class is distinct from Double.class). It’s also possible, for a more flexible solution, to call getConstructors( ), which returns an array of the possible constructors.
What comes back from getConstructor( ) is a reference to a Constructor object (part of java.lang.reflect). You call the constructor dynamically with the method newInstance( ), which takes an array of Object containing the actual arguments. This array is again created using the Java 1.1 syntax:
In this case, however, the double must be placed inside a wrapper class so that it can be part of this array of objects. The process of callingnewInstance( ) extracts the double, but you can see it is a bit confusing-an argument might be a double or a Double, but when you make the call you must always pass in a Double. Fortunately, this issue exists only for the primitive types.
Once you understand how to do it, the process of creating a new object given only a Class reference is remarkably simple. Reflection also allows you to call methods in this same dynamic fashion.
Of course, the appropriate Class reference might not be in the trashTypes list. In this case, the return in the inner loop is never executed and you’ll drop out at the end. Here, the program tries to rectify the situation by loading the Class object dynamically and adding it to the trashTypes list. If it still can’t be found something is really wrong, but if the load is successful then the factory method is called recursively to try again.
As you’ll see, the beauty of this design is that this code doesn’t need to be changed, regardless of the different situations it will be used in (assuming that all Trash subclasses contain a constructor that takes a single double argument).