A problem with the
Above design scenario is that you still need a central location where you must know all types of objects: within the Factory () method. If you frequently add new types to your system, the factory () method is modified for each new type. If you're really upset about this, try going one step further and moving all the information about the type-including its creation-into the class that represents that type. As a result, each time you add a new type, the only thing you need to do is inherit from a class.
To move information about a type creation into a specific type of trash, you must use the "prototype" (prototype) paradigm (from the book "Design Patterns"). The basic idea here is that we have a sequence of Master objects, making one for each type that interests us. The objects in this sequence can only be used for the creation of new objects, and the operation is similar to the clone () mechanism built into the Java root class object. In this case, we name the Clone method Tclone (). When you're ready to create a new object, gather some form of information beforehand and use it to build the type of object we want. Then the control sequence is traversed, and the information on the hand is compared with any appropriate information in the prototype object in the main control sequence. If you find one that fits your needs, clone it.
With this approach, we do not have to embed any creation information in a hard-coded fashion. Each object knows how to reveal the right information and how to clone itself. So when a new type joins the system, the factory () method does not require any change.
to solve the problem of prototyping creation, one approach is to add a number of methods that support the creation of new objects. In Java 1.1, however, if you have a handle to a class object, it already provides support for creating new objects. Using Java 1.1 's "reflection" (introduced in the 11th chapter) technology, even if we only point to a handle to a class object, we can call a builder normally. This solution to the prototype problem is undoubtedly a perfect solution. The
stereotype list is represented indirectly by a list of handles that point to all the class objects you want to create. In addition, if the prototype fails, the factory () method will try to load it because a particular class object is not in the list. By dynamically loading prototypes in this way, the trash class does not need to know what type it is manipulating. Therefore, we do not need to make any changes when we add a new type. As a result, we can easily reuse it in the remainder of this chapter.
: Trash.java//Base class for Trash recycling examples Package c16.trash;
Import java.util.*;
Import java.lang.reflect.*;
Public abstract class Trash {private double weight;
Trash (double wt) {weight = wt;}
Trash () {} public abstract double value ();
Public double weight () {return weight;}
Sums the value of Trash in a bin:public static void Sumvalue (Vector bin) {Enumeration e = Bin.elements ();
Double val = 0.0f; while (E.hasmoreelements ()) {//One kind of RTTI://A dynamically-checked cast Trash t = (Trash) e.nexte
Lement ();
Val + + t.weight () * T.value ();
System.out.println ("Weight of" +//Using RTTI to get type//information about the class:
T.getclass (). GetName () + "=" + T.weight ());
} System.out.println ("Total value =" + val); }//remainder of class provides support for//Prototyping:public static class Prototypenotfoundexception ex Tends Exception {} public static class Cannotcreatetrashexception extends Exception {} private static Vector Trashtypes =
New Vector (); public static Trash Factory (info info) throws Prototypenotfoundexception, cannotcreatetrashexception {f
or (int i = 0; i < trashtypes.size (); i++) {//Somehow determine the new type//To create, and create one:
Class TC = (Class) Trashtypes.elementat (i); if (Tc.getname (). IndexOf (Info.id)!=-1) {try {//Get "dynamic constructor method// Takes a double argument:constructor ctor = Tc.getconstructor (new class[] {double.cl
Ass}); Call the constructor to create a//new Object:return (Trash) ctor.newinstance (New Ob
Ject[]{new Double (Info.data)});
catch (Exception ex) {ex.printstacktrace ();
throw new Cannotcreatetrashexception ();
}
}}//Class is not in the list.
Try to load it,//But it must is in your class path!
try {System.out.println ("Loading" + info.id);
Trashtypes.addelement (Class.forName (info.id));
catch (Exception e) {e.printstacktrace ();
throw new Prototypenotfoundexception (); }//Loaded successfully.
Recursive call//should work this Time:return factory (info);
public static class Info {public String ID;
public double data;
Public Info (String name, double data) {id = name;
This.data = data; }
}
} ///:~
Basic Trash Classes and sumvalue () or as usual. The remainder of this class supports the prototype paradigm. You'll first see two internal classes (set as static properties to make them internal classes that exist for code-only purposes) that describe possible violations. followed by it is a vector trashtypes, used to hold the class handle.
In Trash.factory (), the Info object ID (another version of the info class, different from the one discussed earlier) contains the type name of the trash to be created. This string is compared to the class name in the list. If there is a match, it is the object to be created. Of course, there are many ways to decide which objects we want to create. This method is used because the information that is read from one file can be converted to an object.
After discovering the type of trash (garbage) you want to create, the next step is to "reflect" the method. The GetConstructor () method needs to get its own arguments-an array of class handles. This array represents different parameters and is arranged in their correct order so that the builder we are looking for is used. Here, the array is created dynamically using the array creation syntax of Java 1.1:
New class[] {Double.class}
This code assumes that all trash types have a builder that requires a double value (note that Double.class is different from Double.class). If you consider a more flexible scenario, you can also call GetConstructors () to return an array of available builders.
Returning from GetConstructors () is a handle to a constructor object (which is part of the Java.lang.reflect). We use the method newinstance () to invoke the builder dynamically. The method needs to obtain an object array that contains the actual parameters. This array is also created by the syntax of Java 1.1:
New object[] {new Double (Info.data)}
In this case, the double must be placed inside an encapsulated (container) class, making it truly part of the array of objects. by calling Newinstance (), a double is extracted, but you may find it slightly confusing--the parameter may be double or double, but it must be passed in double when called. Fortunately, this problem exists only in the middle of a basic data type.
Once you understand the specific process, and then create a new object, and just provide it with a class handle, things become very simple. In the present case, the return in the internal loop will never be executed, and we will exit at the end. Here, the program dynamically loads the class object and adds it to the trashtypes (garbage type) list to try to correct the problem. If you still can't find where the problem really is, and the load is successful, repeat the call to factory method and try again.
As you'll see, the biggest advantage of this design is that you don't need to change the code. It works in any case (assuming that all trash subclasses contain a builder to get a single double parameter).
1. Trash Subclass
In order to fit in with the prototype mechanism, the only requirement for each new subclass of trash is to include a builder in it that instructs it to get a double parameter. The "reflection" mechanism of Java 1.1 can be responsible for all remaining work.
The following are different types of trash, each of which has its own file, but it is part of the Trash package (again, for ease of reuse in this chapter):
: Aluminum.java
//The aluminum class with prototyping
package c16.trash;
public class Aluminum extends Trash {
private static double val = 1.67f;
Public aluminum (double wt) {super (WT);}
public Double value () {return val;}
public static void value (double newval) {
val = newval;
}
}///:~
The following is a new type of trash:
: Cardboard.java
//The cardboard class with prototyping
package c16.trash;
public class Cardboard extends Trash {
private static double val = 0.23f;
Public cardboard (double wt) {super (WT);}
public Double value () {return val;}
public static void value (double newval) {
val = newval;
}
}///:~
As you can see, there is nothing special about these classes except the builder.
2. Parse out the trash from the external file
Information about the Trash object is read from an external file. For each aspect of trash, all the necessary information is listed in the file-each line represents one aspect, with a fixed format of "junk (scrap) Name: Value". For example:
C16. trash.glass:54
C16. Trash.paper:22
C16. Trash.paper:11
C16. Trash.glass:17
C16. trash.aluminum:89
C16. trash.paper:88
C16. trash.aluminum:76
C16. trash.cardboard:96
C16. Trash.aluminum:25
C16. Trash.aluminum:34
C16. Trash.glass:11
C16. trash.glass:68
C16. trash.glass:43
C16. Trash.aluminum:27
C16. Trash.cardboard:44
C16. Trash.aluminum:18
C16. trash.paper:91
C16. trash.glass:63
C16. Trash.glass:50
C16. Trash.glass:80
C16. trash.aluminum:81
C16. Trash.cardboard:12
C16. Trash.glass:12
C16. trash.glass:54
C16. Trash.aluminum:36
C16. trash.aluminum:93
C16. trash.glass:93
C16. Trash.paper:80
C16. Trash.glass:36
C16. Trash.glass:12
C16. Trash.glass:60
C16. trash.paper:66
C16. Trash.aluminum:36
C16. Trash.cardboard:22
Note that when a class name is given, the classpath must be included, otherwise the class cannot be found.
To parse it, each row of content is read in and the String method indexof () is used to establish an index of ":". First use the String method substring () to remove the type name of the garbage, then use a static method Double.valueof () to get the corresponding value and convert it to a double value. The trim () method is used to delete extra spaces at both ends of the string.
The trash parser is placed in a separate file because this chapter will continue to use it. As shown below:
: Parsetrash.java//Open a file and parse its contents to//Trash objects, placing each into a Vector package C16.
Trash
Import java.util.*;
Import java.io.*; public class Parsetrash {public static void Fillbin (String filename, fillable bin) {try {BufferedReader
data = new BufferedReader (filename) (new FileReader);
String buf;
while ((buf = Data.readline ())!= null) {String type = buf.substring (0, Buf.indexof (': ')). Trim ();
Double weight = double.valueof (buf.substring buf.indexof (': ') + 1). Trim ()). Doublevalue ();
Bin.addtrash (trash.factory) (New Trash.info (type, weight));
} data.close ();
catch (IOException e) {e.printstacktrace ();
catch (Exception e) {e.printstacktrace (); }//Special case to handle vector:public static void Fillbin (String filename, Vector bin) {Fillbin (filen Ame, new Fillablevector.(bin)); }
} ///:~
In Recyclea.java, we use a vector to hold the trash object. However, other collection types may be considered. To do this, the first version of Fillbin () gets a handle to a fillable. The latter is an interface that supports a method named Addtrash ():
: Fillable.java
//Any object, can is filled with Trash
package c16.trash;
Public interface Fillable {
void Addtrash (Trash t);
}///:~
Anything that supports this interface can be used with fillbin. Of course, the vector does not implement fillable, so it cannot work. Since vector will be used in most examples, it is best to add another overloaded Fillbin () method that takes a vector as its parameter. Using an adapter (Adapter) class, this vector can be used as a Fillable object:
: Fillablevector.java
//Adapter that makes a Vector fillable
package c16.trash;
Import java.util.*;
public class Fillablevector implements fillable {
private Vector v;
Public Fillablevector (Vector vv) {v = vv;}
public void Addtrash (Trash t) {
v.addelement (t);
}
}///:~
As you can see, the only task for this class is to connect the fillable Addtrash () to the vector addelement () method. Using this class, the overloaded Fillbin () method can be used with a vector in Parsetrash.java:
public static void
Fillbin (String filename, Vector bin) {
fillbin (filename, new fillablevector (bin));
}
This scenario applies to any collection class that is frequently used. In addition, the collection class can also provide its own adapter class and implement Fillable (see later, in Dynatrash.java).
3. Repetitive application of prototype mechanism
Now you can see the revised version of Recyclea.java with the prototype technology:
//: Recycleap.java//Recycling with RTTI and prototypes package c16.recycleap; import c16.trash.*;
Import java.util.*;
public class Recycleap {public static void main (string[] args) {Vector bin = new vector ();
Fill up the Trash bin:ParseTrash.fillBin ("Trash.dat", bin);
Vector glassbin = new vector (), paperbin = new vector (), alBin = new vector ();
Enumeration sorter = Bin.elements ();
Sort the Trash:while (sorter.hasmoreelements ()) {Object t = sorter.nextelement ();
RTTI to show class Membership:if (t instanceof Aluminum) albin.addelement (t);
if (t instanceof Paper) paperbin.addelement (t);
if (t instanceof Glass) glassbin.addelement (t);
} trash.sumvalue (AlBin);
Trash.sumvalue (PaperBin);
Trash.sumvalue (Glassbin);
Trash.sumvalue (BIN); }
} ///:~
All trash objects--and Parsetrash and support classes--are now part of a package called C16.trash, so they can be simply imported.
Whether you open a data file that contains trash descriptive information or parse that file, all involved operations are encapsulated in the static (static) method Parsetrash.fillbin (). So now it is not a focus of attention in our design process. In the remainder of this chapter, you will often see that no matter what type of new class is added, Parsetrash.fillbin () will continue to work and will not change, which is undoubtedly an excellent design.
Referring to the creation of objects, this scenario has really "localized" the changes required to add new types to the system. But in the process of using RTTI, there is a serious problem, which has been clearly revealed. The program works well on the surface, but it always detects a new type of scrap that can't be "cardboard"-even though there is a cardboard type in the list cardboard! The reason for this is that it is entirely due to the use of Rtti. Rtti will only look for what we tell it to look for. Rtti here the wrong use is that "each type in the system" is tested rather than testing only one type or subset of a type. As you will see later, there are other ways in which you can use the Multifractal feature when testing each type. But if you use Rtti in this form too much, and you add a new type to your system, it's easy to forget to make the right changes in your program to bury bugs that are hard to find later. Therefore, it is necessary to avoid using rtti in this case, not just to look good-but also to produce code that is easier to maintain.