COMP 202: Principles of Object-Oriented Programming II← Marine Biology Simulation: Factories Making Factories →
|Home — Fall 2008|||||Information|||||Resources|||||Exam Resources|||||OwlSpace|||||COMP 201|
Factories are used to create families of related objects without having to specify the exact class. The use of factories decouples the creation of objects from their use. The same code functions for different concrete classes; in fact, using dynamic class loading, classes can be loaded into the program that did not even exist when the original program was written. A factory also hides the implementation and internal composition and reveals just the interface of a class.
In our implementation of the Marine Biology Simulation, we needed to dynamically create arbitrary environments (you can imagine environments as a different shapes and types of aquariums). To decouple the environments from their creation, we wanted to use factories and dynamic class loading. The user of the simulation should be able to select the desired environment type and choose its parameters in a GUI and then create it.
Unfortunately, environments did require a single set of values. Some environment types needed no parameters, some two, others potentially even more. We did not want to constrain the author of future environments, so the normal normal use of a factory pattern did not work.
These lecture notes document how we solved this problem. For illustration purposes, we have simplified to problem to the creation of arbitrary shapes. Let us go back to the beginning of the course...
Look at this implementation of different shapes.
The constructor of Circle takes one parameter, the radius. The constructor of the Rectangle class accepts two parameters for the width and the height.
In general, a shape's constructor may require an arbitrary number of parameters. Therefore, the factory methods in the shape factory also require varying numbers of parameters.
Question: Can we write an abstract factory that creates a shape?
Answer: No, not directly. At the abstract level, the factory does not know how many arguments the shape's constructor needs. We therefore cannot have an abstract class in the factory that can be used to create all shapes.
An abstract factory method to create a circle could look like this:But for a rectangle, it would have to look like this:
Question: Who knows what it takes to create a shape?
Answer: The shape itself is the only class that knows how to create itself. Only the shape knows what kind of factory we need to create the shape.
Take another look at the situation: We have a union pattern of shapes that all behave similarly. They represent geometric shapes, they can return their area, and they can probably draw themselves on the screen. We do not want to create them directly using their constructors, though, because that would limit us to very specific shapes. We want to abstract out the creation and use factories.
These factories should encapsulate the creation process of different shapes. They are to create different shapes, but their use should be identical. Using a rectangle factory should be no different from using a circle factory. The problem is that the factories require a different number of parameters, and only the shapes know how many and which ones.
How can we create these factories? We cannot explicitly create a factory since that would limit the number of parameters and thus the possible shapes that can be created. We need to abstract out the creation of the factories. What pattern do we use for this task? The factory pattern again. Since the shapes know how to create their own factories, the shapes should act as factories for the factories.
Summary: A shape class contains a factory method that makes an abstract factory. This factory can then be used to create the kind of shape that created it.
The shapes contain a factory method. Ideally, it would be declared abstractly in IShape. It should also be static, so we don't have to create a new object just to use it. Its signature could look like this:
Unfortunately, Java does not allow the combination of static and abstract. Since we do need all of the shapes to behave identically, we cannot sacrifice the abstract behavior, so we need to give up on the static method and create a shape object before we can use the factory method.>
This creates another dilemma: We need to create a shape, but we just said we do not generally know how to create a shape, since the shapes' constructors might have an arbitrary number of parameters. If we still want to be able to create shapes in the same way, we have to introduce a constructor that is identical in all shapes. Here, we choose a constructor without any parameters. Thus, all shapes must provide a constructor like this:
This is really unfortunate. It violates our requirement that all objects should be fully initialized when they get created. Clearly, a rectangle that requires a width and a height cannot be initialized to meaningful values in a constructor that does not take any parameters. The programmer has to make sure that only the factory method is used if the object was created using the no-parameter constructor.
Now we can use dynamic class loading to load any shape, create the correct factory that can make this specific kind of shape, and use it:
Here is the UML class diagram:
Summary: A shape has an abstract method that can make the shape's corresponding factory. This factory method is not static, so we need to create a shape first to use it. Since shapes normally can have differing constructors, we added a no-parameter constructor that is shared among all shapes. We can use this constructor to create a (partially initialized) shape that then creates the factory, which can later create the (fully initialized) shape.
The program now has the ability to make factories specifically designed to create a certain type of shape. But what exactly happens in the makeShape method? The factory still needs to gather all the necessary information for the shape it is to create.
In this case, the pieces of information come from the user. We want to allow the user to enter the dimensions of a shape in a GUI. This can conveniently be solved by making the factory be a shape factory and a JPanel at the same time. The abstract AShapeFactory class extends JPanel and adds its own abstract methods to it. Since the AShapeFactory combines JPanel's concrete behavior and the factory's abstract behavior, it is an abstract class, not an interface.
Once the correct shape factory has been instantiated using the dynamic class loading code above, the program's view adds the factory to its content pane. It can do so because the factory is-a JPanel. When the shape's makeFactory method created the factory, it knew what information the user needs to provide, so it could already add all the GUI components to the JPanel (the factory itself!). The makeShape method reads the values out of the GUI components and creates the shape.
Here is the makeFactory method for a rectangle. It contains the anonymous shape factory:
If the user selects a new factory or hits the change button, the ActionListener executes the factory's makeShape factory method. This method combines all the necessary information and invokes the shape's constructor, returning a fully initialized shape.
We combine the necessary data and the creation of a shape in one class, the factory. Thus, the pieces of information do not have to be passed around, and encapsulation is never violated. Since we realize the factories as anonymous objects inside the shapes, the shape and the factory are grouped tightly together to represent their close relationship.
Summary: In our program, the shape constructors need information from user input. We therefore make the shape factory both factory and a GUI component.
This constellation of design patterns is widely applicable; input does not need to come from the user. When our Marine Biology Simulation needed to save and reload environment data, we applied similar means to write and read environment-specific files.
Please take a look at the source code and run the applet.
Copyright © 2008-2010 Mathias Ricken and Stephen Wong