Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
Creating Objects Using the Collections Factory
The Commons project Collections contains a number of extensions that help Java programmers carry out everyday programming tasks . One part of the library is the factory helper classes. Consider Listing 3.8, which implements a modified version of the class SharedFactory .
Listing 3.8
|
final class SharedLangFactory { static final SomeFunctionality _orig = new SomeFunctionality(); public static Factory getFactory() { return FactoryUtils.prototypeFactory( _orig); } }
|
In Listing 3.8, the class SharedLangFactory has a single method called getFactory . The purpose of the method getFactory is to retrieve a standard interface instance of the langs package factory instance. Relative to the patterns-based factory, the method getFactory is a reference to the factory that will instantiate the object. This means there are two levels of indirection. Now consider Listing 3.9, which finishes off Listing 3.8.
Listing 3.9
|
Factory fac = SharedLangFactory.getFactory(); InterfaceToBeShared intrf = (InterfaceToBeShared)fac.create(); intrf.availability();
|
In Listing 3.9, the method getFactory retrieves an interface-based instance of the interface Factory . The instance fac is then used to instantiate an interface-based implementation of the interface InterfaceToBeShared using the method call create . Once the object has been instantiated , the method availability can be called.
This method of allocation may seem an exercise in overkill because you have to create a factory before you can use it. If you used the instantiated factory in this context, then it absolutely would be overkill. However, that is not the purpose of this library. Rather, its purpose is to be able to generically define a factory that will be used throughout the subsystem or application. Then, that factory can be defined to execute in different contexts without having the client update its code. For example, it is possible to define a factory that will either create a new instance every time or use a singleton of the implementation to instantiate.
Technical Details for the lang Factory
Tables 3.1 and 3.2 contain abbreviated details necessary to use the lang factory package.
Item | Details |
---|---|
CVS repository | |
Directory within repository | collections |
Main packages used | org.apache.commons.collections |
Class/Interface Details | |
[collections].functor.Factory | A neutral interface used to instantiate an object |
[collections].functor.FactoryUtils | The main class that contains all of the functions used to instantiate the different implementations of the Factory interface |
[collections].functor.FactoryException | An exception that is thrown if the Factoryclasses have encountered a problem |
Instantiating a Cloned Object
The Listings 3.8 and 3.9 are functionally correct. However, they would not work because the object to be instantiated is the wrong type of object. The idea behind the Collections factory is to instantiate objects based on already existing instances. The Collections factory uses this general approach so that it's possible to define a generic default state and have that state propagated whenever the object is instantiated. As a result, a component, subsystem, or application requires an initialization stage, which initializes the objects that will be created in the execution of the program.
A class that could be used in the Collections factory is shown in Listing 3.10.
Listing 3.10
|
package com.devspace.jseng.create; public class ObjectToBeCreated implements InterfaceToBeShared, Cloneable { public Object clone() throws CloneNotSupportedException { ObjectToBeCreated cls = (ObjectToBeCreated)super.clone(); return cls; } public void availability() { System.out.println( "Yes we have bananas today"); } }
|
There are several things to note in Listing 3.10. The first is that the object must be declared as public, which means nested classes and inner classes will not work. You must do this because the implementation of the clone factory requires public classes. In order for the lang package clone factory to work, the object to be created must have a public scoped method clone. The method clone is responsible for instantiating a new instance of the object.
Implementing the Method clone
Implementing the method clone is simple, but the details of the implementation require great care. The purpose of cloning is to be able to create a brand-new instance of the current object. However, by itself the method clone does help the developer implement the method. For example, consider the class declaration in Listing 3.11.
Listing 3.11
|
class NonDeepCloneExample implements Cloneable { private ArrayList _array; public Object clone() throws CloneNotSupportException { NonDeepCloneExample ex = new NonDeepCloneExample(); ex._array = _array; return ex; } }
|
In Listing 3.11, the method clone allocates a new instance of the object NonDeepCloneExample . Then, the data member _array is assigned to the data member _array of the newly allocated object ex . The problem with this way of copying is that it is not incorrect in a technical sense. Doing an assignment is OK because, once the local instance object goes out of scope, the cloned instance will still be able to reference the array list. The problem is at a higher level. An assignment of the array list means that two object instances will point to the same array list. This means any changes made by one instance will automatically be reflected in the other instance, which we don't want. A quick and simple solution would be to change the implementation of the method clone to what is shown in Listing 3.12.
Listing 3.12
|
public Object clone() throws CloneNotSupportException { NonDeepCloneExample ex = new NonDeepCloneExample(); ex._array = (ArrayList)_array.clone(); return ex; }
|
The modified version of the method clone in Listing 3.12 would seem to be the ideal solution because the method clone of the array list is used. However, this may not be true in reality. The reality of what happens lies in the implementation of the method ArrayList.clone , as shown in Listing 3.13.
Listing 3.13
|
public Object clone() { try { ArrayList v = (ArrayList)super.clone(); v.elementData = new Object[size]; System.arraycopy(elementData, 0, v.elementData, 0, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // This shouldn't happen throw new InternalError(); } }
|
In Listing of 3.13, which was taken from the class ArrayList , the method clone creates a new array to hold the object references. However, instead of copying the individual array elements, only the object references are copied . This means that while the class method ArrayList.clone creates a new instance of the class ArrayList , the elements are still referenced by two instances of ArrayList . Now, instead of referencing the same ArrayList instance, both instances of the class NonDeepCloneExample are referencing the same set of objects referenced by two different instances of the ArrayList . The lesson is that the overall idea of the method clone works, except that most clone methods are shallow clone implementations.
Implementing a Deep Clone
A shallow clone is when an object clones itself as well as the data members that the object references. However, with a shallow clone, the references of the individual object references are not cloned. To convert a shallow clone into a deep clone, each individual object has to be cloned, as shown in Listing 3.14.
Listing 3.14
|
class NonDeepCloneExample implements Cloneable { private ArrayList _array; public Object clone() throws CloneNotSupportedException { NonDeepCloneExample ex = new NonDeepCloneExample(); ex._array = (ArrayList)_array.clone(); for( int c1 = 0; c1 < _array.size(); c1 ++) { OtherClass obj = (OtherClass)_array.get( c1); ex._array.add( obj.clone()); } return ex; } }
|
In the modified version of the class method NonDeepCloneExample . clone shown in Listing 3.14, the individual array list elements are iterated and cloned. Listing 3.14 makes extensive use of the method clone . Often, in situations similar to that shown in Listing 3.14, the individual classes are allocated and then initialized . That methodology is fine and works. However, in the context of the Collections factory, where it is required to support the method> clone , it is better to be consistent and use the method clone everywhere. Not being consistent could cause specific bugs to be introduced with version changes.
One modification in Listing 3.14 could have been to use the class Object instead of the class OtherClass when extracting the elements from the array list. The problem with this approach is that the method clone is protected and hence not publicly available. To make Listing 3.14 work, you must cast the class to a specific type, which means defining a generic interface. The generic interface would support the method clone and be public scoped. The generic interface would be used to clone an object, so the generic interface would have to support the interface Cloneable . The interface Cloneable itself does not have any methods, but it's required when you use the method call super.clone . Listing 3.15 shows how to define the generic interface.
Listing 3.15
|
interface BaseOfAllInterfaces extends Cloneable { public Object clone() throws CloneNotSupportedException; }
|
When using this interface, you can cast the instance to the interface BaseOfAllInterfaces and use that instead of the class OtherClass in Listing 3.14.
One last note before we discuss the other parts of the Collections factory. Traditionally, you must implement the interface Cloneable to indicate that an object instance can be instantiated. The Collections factory is flexible and does not require explicit support of the interface Cloneable . Not using the Cloneable interface would require using the new keyword in Listing 3.10 instead of super.clone.
Instantiating a Singleton Object
In Listing 3.8, a class was instantiated using the class method FactoryUtils.prototypeFactory to instantiate an object instance. With each usage of the Factory.create method, another object was instantiated. However, it is sometimes desirable to return the same object, which is called a singleton . This allows you to define an instance of a class that is referenced whenever the interface method Factory.create is called. If we modified Listing 3.8 to add singleton functionality, you would get Listing 3.16.
Listing 3.16
|
final class SharedLangFactory { static final ObjectToBeCreated _cloned = new ObjectToBeCreated(); static final ObjectToBeCreated _singleton = new ObjectToBeCreated(); public static Factory getClonedFactory() { return FactoryUtils.prototypeFactory( _cloned); } public static Factory getSingletonFactory() { return FactoryUtils.constantFactory( _singleton); } }
|
The class SharedLangFactory in Listing 3.16 has changed fundamentally from what it was in Listing 3.8. The class has become more like a multiple objects factory, described in Chapter 2. The method getClonedFactory is using the cloned instance method. What concerns us right now is the method getSingletonFactory . In the implementation of the method, the class method FactoryUtils.constantFactory is called. The parameter passed to the class method FactoryUtils.constantFactory is another static object. However, the data member _singleton is an important object. Whenever the interface method Factory.create is called, the reference to the data member _singleton is returned.
Knowing What Is a Singleton and What Is Not
Knowing that you can create a singleton from the lang factory is good but then again you need to know what is and is not a singleton. In Listing 3.16, two data members ” _cloned and _singleton ”are defined. Each data member is a singleton in the class SharedLangFactory . When the methods getClonedFactory or getSingletonFactory are called, the interface instances of Factory are separate instances. This means that five calls to the method getClonedFactory yield five different instances of the interface Factory .
However, these different instances are misleading. Consider the implementation of ConstantFactory shown in Listing 3.17.
Listing 3.17
|
private static class ConstantFactory implements Factory, Serializable { private final Object iConstant; private ConstantFactory(Object constant) { super(); iConstant = constant; } public Object create() { return iConstant; } }
|
The constructor of the class ConstantFactory in Listing 3.17 requires a parameter, which is the object to be used as a basis for the factory. The data member iConstant references the passed-in object. Relating this back to Listing 3.16, the object constant iConstant is the data member _singleton . Therefore, if five constant factories are instantiated, each iConstant will reference the same reference that the data member _singleton references.
This coding pattern does impact how the Collections factory operates. For example, if the ConstantFactory is passed a different object instance, then a different singleton is created. In addition, in the case of the clone factory, the method clone may need synchronization, since several objects may be cloning the same instance. The only place where this is critical is if the clone operation requires access to a state that may not be available when the object is to be cloned. Of course, though, programmers would never do that since it's a bad idea to clone a state that morphs. Nonetheless, it is important when you use the lang factory that the object used as a basis for the individual factories is a global item, as we saw in Listing 3.16.
Instantiating a Reflected Object
The last type of instantiation that you can perform using the Collections factory is the reflection instantiation. The reflection approach is the simplest and least complicated. If we modify the class SharedLangFactory that we saw in Listing 3.16 and add the reflection code, we'll get what's shown in Listing 3.18.
Listing 3.18
|
final class SharedLangFactory { static final ObjectToBeCreated _cloned = new ObjectToBeCreated(); static final ObjectToBeCreated _singleton = new ObjectToBeCreated(); static final ObjectToBeCreated _reflected = new ObjectToBeCreated(); public static Factory getClonedFactory() { return FactoryUtils.prototypeFactory( _cloned); } public static Factory getSingletonFactory() { return FactoryUtils.constantFactory( _singleton); } public static Factory getReflectionFactory() { return FactoryUtils.reflectionFactory( _reflected.getClass()); } }
|
In Listing 3.18, the data member _reflected and method getReflectionFactory are the new factory classes that use reflection. To use the reflection factory, an object is still instantiated. However, instead of passing in a reference to the instantiated object, a reference to the class object is passed. Another way of passing the class would be to use the class definition (as defined by the class type) using the data member ObjectToBe-Created.class. The reflection factory uses the built-in reflection techniques of the Java Virtual Machine (VM), which is why this method is the simplest of all lang factory methods.
Категории