Saturday, March 21, 2009

Challenge Five Answer: Decorator Design Pattern and Reflection

One approach to this problem is to have a class representing each of the possible pizza types and extra toppings. For example, one class might be ThinCrustOnionPepperoni whereas another might be ChicagoStyleAnchovy. Thus, if the user selects the Thin Crust radio button and Onion check box, an instance of ThinCrustOnion would be instantiated and used for the source of data needed to display the description and the cost. This is a brittle solution because it is very difficult to add and subtract components from the menu. New classes with all the various combinations of components must be pre-constructed and this type of class definition isn't necessary when we can build the class dynamically. The Decorator Design Pattern is ideally suited for this kind of situation because it permits us to add responsibilities to our constructed object dynamically. The code discussed in the following can be downloaded here.

The GOF text speaks of the Decorator Design Pattern as a "degenerate composite with only one component." Java programmers encounter this design pattern when they begin using classes in the package in a manner similar to this:

DataInputStream dis = new DataInputStream(new FileInputStream(new File("name")));.

All of the classes referenced in the preceding statement are subclasses of and each one has a constructor that takes an InputStream argument. How can this practice of dynamically building responsibilities into the desired class be employed in our pizza place?

Consider the following statement as an alternative to our previous solution where pre-constructed classes represented the range of pizza types and toppings.

PizzaComponent pizza = new Onion(new Pepperoni(new ThinCrust()));

An application employing this type of solution to our problem is easier to change. All that is required is to add or omit the relevant component and change the GUI. An application using this approach is available here.

However, since this discussion appears in the context of programming without if's, we still have more progress to make. Our application is still using conditional logic to determine which combination of radio button and check boxes was selected and then subsequently builds the desired pizza. Wouldn't it be nice to have a way to build a list of the desired components and then when the user proceeds to the checkout, the list of components is used to dynamically build our pizza object? Then it wouldn't be necessary to use any conditional logic to determine which components were to be included as decorators on our pizza. We can't build a list of object instances because the components are built from instances of each other. Can our list be composed of the class names? Yes - and we can use reflection combined with the Decorator to build our pizza without the use of conditional logic.

The following discussion introduces us to another GOF design pattern, the Builder Design Pattern, but we will save the discussion of Builder for another time. In the application available here a few new features have been introduced to obviate the need for conditional logic. Subclasses of JRadioButton and JCheckBox, SelfListeningJRadioButton and SelfListeningJCheckBox, respectively, were created to relieve the GUI from having to determine (with conditional logic) which button or box was selected. In other words we are now giving each button or checkbox the responsibility of listening to itself and also knowing what to do when it is selected. Subclasses of these self-listening components, such as OnionCheckBox are instantiated and passed a reference to an instance of PizzaComponentBuilder which is used to store the list of class names of the components selected by the user. In other words, each time a user selects a radio button or a checkbox the class name of that pizza component is added to a List on the builder object.

There are more features included in this application that need to be discussed. To deal with users changing their minds while selecting components, the capability to unselect a checkbox was included. Since selecting a checkbox results in adding the class name to the list in the builder, de-selecting a checkbox must remove the class name from the list. This capability was added with the use of the Strategy Design Pattern (discussed already in Challenge One: The Bergin Calculator application) and accounts for the 'on' and 'off' strategies associated with each of the components. With the use of these strategies the checkbox components do not need to ask themselves what state they are in to perform their responsibilities.

Because the method we are using in this application is much easier to implement, we have included the capability to select one of the other pizza types, ie., Chicago Style, Pan or M'Waukee Style. Again, the Strategy Design Pattern was used to obviate the need to use conditional logic to determine which of the pizza types was to serve as the base component in the construction of the pizza.

Now we consider the way the PizzaComponentBuilder constructs the pizza object from the List containing the class names of the components. As mentioned above we combine the use of reflection with the Decorator Design Pattern to build our pizza object. Consider the method shown below which contains all that is needed. The strComponents object is a List which contains the class names of the components as Strings.

public PizzaComponent buildComponentUsingReflection()
newComponent = currentStrategy.getBaseComponent();
Object[] arg = { newComponent };
iterator = strComponents.iterator();
while (iterator.hasNext())
someClass = Class.forName((String);
constructors = someClass.getConstructors();
newComponent = (PizzaComponent) constructors[0].newInstance(arg);
catch (Exception ex3)
System.out.println("something wrong in PizzaComponentBuilder 3 " + ex3);
arg[0] = newComponent;
return newComponent;

The first statement from the method above determines which pizza type was selected, ie., Thin Crust, Pan, etc., and the second step assigns to the object array the inner most (nucleus) component of our pizza object. The iteration is started that goes through the list of class names that will be the components used to build the pizza object. The first statement in the try-catch block creates an instance of Class that represents the class identified by the class name (String) in the array. The first time through the iteration finds this class to be one of the pizza types which was placed in the array in the above-described steps. The next step obtains the array of Constructors (Constructors[])which was defined with class scope and not shown in the method (see code package). The next step containing the invocation of contructors[0].newInstance(PizzaComponent) is not immediately obvious. This is the step that instantiates the class represented in the first step, which will be the pizza type the user selected. For purposes of discussion, assume the type is Thin Crust and therefore the object will be an instance of ThinCrust class. The signature of Contructor.newInstance requires the arguments to the constructor to be passed as an array containing the argument objects. In our case the array argument, 'arg' contains only one component, the instance of ThinCrust set in the second statement of the method. Then, once the new PizzaComponent is instantiated and we leave the try-catch block, we assign to the 'arg' array the object that was just instantiated. On the subsequent iteration, this is the argument used in the constructor for the next level of our decorated pizza object.

No comments:

Post a Comment