Chapter 4. The Factory Method

We have just seen a couple of examples of the simplest of factories. The factory concept recurs throughout OO programming, and we find examples embedded in Java itself (such as the SocketFactory class) and in other design patterns (such as the Builder pattern, discussed in Chapter 7). In these cases, a single class acts as a traffic cop and decides which subclass of a single hierarchy will be instantiated.

The Factory Method pattern is a clever but subtle extension of this idea, where no single class makes the decision as to which subclass to instantiate. Instead, the superclass defers the decision to each subclass. This pattern does not actually have a decision point where one subclass is directly selected over another subclass. Instead, a program written using this pattern defines an abstract class that creates objects but lets each subclass decide which object to create.

We can draw a pretty simple example from the way that swimmers are seeded into lanes in a swim meet. When swimmers compete in multiple heats in a given event, they are sorted to compete from slowest in the early heats to fastest in the last heat and arranged within a heat with the fastest swimmers in the center lanes. This is called straight seeding.

Now, when swimmers swim in championships, they frequently swim the event twice. During preliminaries, everyone competes and the top 12 or 16 swimmers return to compete against each other at finals. In order to make the preliminaries more equitable, the top heats are circle seeded, so that the fastest three swimmers are in the center lane in the fastest three heats, the second fastest three swimmers are in the lanes next to center lane in the top three heats, and so on.

So how do we build some objects to implement this seeding scheme and illustrate the Factory Method pattern? First, we design an abstract Event class.

public abstract class Event    {
    protected int    numLanes;        //used in each subclass
    protected Vector swimmers;

   public abstract Seeding getSeeding();
   public abstract boolean isPrelim();
   public abstract boolean isFinal();
   public abstract boolean isTimedFinal();
}

This defines the methods without our having to fill in the methods. Then we can derive concrete classes from the Event class, called PrelimEvent and TimedFinalEvent. The only difference between these classes is that one returns one kind of seeding and the other returns a different kind of seeding.

We also define an abstract Seeding class having the following methods:

public abstract class Seeding {
   public abstract Enumeration getSwimmers();
   public abstract int getCount();
   public abstract int getHeats();
}

Next, we create two concrete seeding subclasses: StraightSeeding and CircleSeeding. The PrelimEvent class will return an instance of CircleSeeding, and the TimedFinalEvent class will return an instance of StraightSeeding. Thus we see that we have two hierarchies: one of Events and one of Seedings. We see these two hierarchies illustrated in Figure 4.1.

The class relationships between Event and Seeding classes.

Figure 4.1. The class relationships between Event and Seeding classes.

In the Event hierarchy, you will see that both derived Event classes contain a getSeeding method. One of them returns an instance of StraightSeeding, and the other returns an instance of CircleSeeding. So you see, there is no real factory decision point as we had in our simple Namer example. Instead, the decision as to which Event class to instantiate determines which Seeding class will be instantiated.

While it looks like there is a one-to-one correspondence between the Seeding and Event class hierarchies, there needn't be. There could be many kinds of Events and only a few kinds of Seeding that they use.

The Swimmer Class

We haven't said much about the Swimmer class, except that it contains a name, club, age, seed time, and place to put the heat and lane after seeding. The Event class reads in the Swimmers to a Vector from some database (a file, in this example) and then passes that Vector to the Seeding class when we call the getSeeding method for that event.

The Event Classes

We have seen the abstract base Event class earlier. We actually use it to read in the swimmer data (here from a file) and pass it on to instances of the Swimmer class to parse.

public abstract class Event {
    protected int    numLanes;
                                     //number of lanes
    protected Vector swimmers;       //list of swimmers

    public Event(String filename, int lanes) {
        numLanes = lanes;
        swimmers = new Vector();
        //read in swimmers from file
        InputFile f = new InputFile(filename);
        String s = f.readLine();
        while(s != null) {
            Swimmer sw = new Swimmer(s);
            swimmers.addElement(sw);
            s = f.readLine();
        }
        f.close();
    }
    public abstract Seeding getSeeding();
    public abstract boolean isPrelim();
    public abstract boolean isFinal();
    public abstract boolean isTimedFinal();
}

Our PrelimEvent class just returns an instance of CircleSeeding,

public class PrelimEvent extends Event {

//class describes an event that will be swum twice
    public PrelimEvent(String filename, int lanes) {
        super(filename, lanes);
    }
    //return circle seeding
    public Seeding getSeeding() {
        return new CircleSeeding(swimmers, numLanes);
    }
    public boolean isPrelim() {
        return true;
    }
    public boolean isFinal() {
        return false;
    }
    public boolean isTimedFinal() }
        return false;
    }
}

Straight Seeding

In actually writing this program, we'll discover that most of the work is done in straight seeding; the changes for circle seeding are pretty minimal. So we instantiate our StraightSeeding class and copy in the Vector of swimmers and the number of lanes.

public class StraightSeeding extends Seeding {
      protected Vector    swimmers;
      protected Swimmer[] swmrs;
      protected int       numLanes;
      protected int[]     lanes;
      protected int       count;
      protected int       numHeats;

public StraightSeeding(Vector sw, int lanes) {
      Swimmers = sw;
      numLanes = lanes;
      count = sw.size();
      calcLaneOrder();
      seed();
    }

Then, as part of the constructor, we do the basic seeding:

protected void seed() {
         //loads the swmrs array and sorts it
         sortUpwards();

         int lastHeat = count % numLanes;
         if(lastHeat < 3)
            lastHeat = 3;     //last heat must have 3 or more
         int lastLanes = count - lastHeat;
         numHeats = count / numLanes;
         if(lastLanes > 0)
            numHeats++;
         int heats = numHeats;

         //place heat and lane in each swimmer's object
         int j = 0;

         for(int i = 0; i < lastLanes; i++) {
            Swimmer sw = swmrs[i];

            sw.setLane(lanes[j++]);
            sw.setHeat(heats);
            if(j >= numLanes) {
               heats--;
               j=0;
            }
         }
         //Add in last partial heat
         if(j < numLanes)
            heats--;
         j = 0;
         for(int i = lastLanes-1; i<count; i++) {

            Swimmer sw = swmrs[i];
            sw.setLane(lanes[j++]);
         }
         //copy from array back into Vector

         Swimmers = new Vector();
         for(int i = 0; i < count; i++)
            Swimmers.addElement(swmrs[i]);
}

This makes the entire array of seeded Swimmers available when we call the getSwimmers method.

Circle Seeding

The CircleSeeding class is derived from StraightSeeding, so it copies in the same data.

public class CircleSeeding extends StraightSeeding {

    public CircleSeeding(Vector sw, int lanes) {
        super(sw, lanes);  //straight seed first
        seed();
    }
//--------------
    protected void seed() {
        int circle;

        super.seed();      //do straight seed as default
        if(numHeats >= 2 ) {
           if(numHeats >= 3)
                circle = 3;
            else
                circle = 2;
            int i= 0;
            for(int j = 0; j < numLanes; j++) {
                for(int k = 0; k < circle; k++) {
                    swmrs[i].setLane(lanes[j]);
                    swmrs[i++].setHeat(numHeats - k);
                }
            }
        }
    }
}

Since the constructor calls the parent class constructor, it copies the swimmer Vector and lane values. Then a call to super.seed() does the straight seeding. This simplifies things because we will always need to seed the remaining heats by straight seeding. Then we seed the last 2 or 3 heats as shown previously, and we are done with that type of seeding as well.

Our Seeding Program

In this example, we took from the Web a list of swimmers who had competed in the 500-yard freestyle and the 100-yard freestyle and used them to build our TimedFinalEvent and PrelimEvent classes. You can see the results of these two seedings in Figure 4.2.

Straight seeding of the 500-yard and circle seeding of the 100-yard freestyles.

Figure 4.2. Straight seeding of the 500-yard and circle seeding of the 100-yard freestyles.

Other Factories

Now one issue that we have skipped over is how the program that reads in the swimmer data decides which kind of event to generate. We finesse this here by calling the two constructors directly:

    events.addElement(new TimedFinalEvent("500free.txt", 6));
    events.addElement(new PrelimEvent("100free.txt", 6));

Clearly, this is an instance where an EventFactory may be needed to decide which kind of event to generate. This revisits the simple factory we began the discussion with.

When to Use a Factory Method

You should consider using a Factory method under the following circumstances:

  • A class can't anticipate which kind of class of objects that it must create.

  • A class uses its subclasses to specify which objects it creates.

  • You want to localize the knowledge of which class gets created.

There are several variations on the Factory pattern.

  1. The base class is abstract, and the pattern must return a complete working class.

  2. The base class contains default methods and is subclassed only when the default methods are insufficient.

  3. Parameters are passed to the factory telling it which of several class types to return. In this case, the classes may share the same method names, but each may do something quite different.

Thought Question

  1. Seeding in track is carried out from inside to outside lanes. What classes would you need to develop to carry out track-like seeding?

Programs on the CD-ROM

Program Description

FactoryFactory MethodShowSeeding.java

Illustrates the Factory Method pattern.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset