Chapter 9. The Factory Method

We’ve just seen a couple of examples of the simplest of factories. The factory concept recurs throughout object-oriented programming, and we find a few examples embedded in C# itself and in other design patterns (such as the Builder pattern). 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 class. Instead, programs written to this pattern define 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 referred to as 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 one another at finals. In order to make the preliminaries more equitable, the top heats are circle seeded: The fastest three swimmers are in the center lane in the fastest three heats, the second fastest three swimmers are in the next to center lane in the top three heats, and so forth.

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


public abstract class Event     {
      protected int numLanes;
      protected ArrayList swimmers;
      public Event(string filename, int lanes) {
            numLanes = lanes;
            swimmers = new ArrayList();
            //read in swimmers from file
             csFile f = new csFile(filename);
            f.OpenForRead ();
            string s = f.readLine();
            while (s != null) {
                    Swimmer sw = new Swimmer(s);
                    swimmers.Add (sw);
                    s = f.readLine();
            }
            f.close();
      }
      public abstract Seeding getSeeding();
      public abstract bool isPrelim();
      public abstract bool isFinal();
      public abstract bool isTimedFinal();
}

Note that this class is not entirely without content. Since all the derived classes will need to read data from a file, we put that code in the base class.

These abstract methods simply show the rest of a complete implementation of an Event class. Then we can implement 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 with the following methods.


public abstract class Seeding      {
             protected int       numLanes;
             protected int[]     lanes;
             public abstract IEnumerator getSwimmers();
             public abstract int getCount();
             public abstract int getHeats();
             protected abstract void seed();
             //--------------------------------
             protected void calcLaneOrder() {
             //complete code on CD
             }
      }

Note that we actually included code for the calcLaneOrder method but omit the code here for simplicity. The derived classes then each create an instance of the base Seeding class to call these functions.

We can then 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.

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

Figure 9-1. Seeding diagram showing Seeding interface and derived classes

image

While it looks like there is a one-to-one correspondence between the two class hierarchies, there needn’t be. There could be many kinds of Events and only a few kinds of Seeding used.

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 from some database (a file in our example) and then passes that collection to the Seeding class when you call the getSeeding method for that event.

The Events Classes

We have seen the previous abstract base Events class. In actual use, we use it to read in the swimmer data and pass it on to instances of the Swimmer class to parse. The base Event class has empty methods for whether the event is a prelim, final, or timed final event. We fill in the event in the derived classes.

Our PrelimEvent class just returns an instance of CircleSeeding.


public class PrelimEvent:Event   {
      public PrelimEvent(string filename, int lanes):
             base(filename,lanes) {}
             //return circle seeding
      public override Seeding getSeeding() {
             return new CircleSeeding(swimmers, numLanes);
      }
      public override bool isPrelim() {
             return true;
      }
      public override bool isFinal() {
             return false;
      }
      public override bool isTimedFinal() {
             return false;
      }
}

Our TimedFinalEvent class returns an instance of StraightSeeding.


public class TimedFinalEvent:Event  {

      public TimedFinalEvent(string filename,
             int lanes):base(filename, lanes) {}
      //return StraightSeeding class
      public override Seeding getSeeding() {
             return new StraightSeeding(swimmers, numLanes);
      }
      public override bool isPrelim() {
             return false;
      }
      public override bool isFinal() {
             return false;
      }
      public override bool isTimedFinal() {
             return true;
      }
}

In both cases our events classes contain an instance of the base Events class, which we use to read in the data files.

StraightSeeding

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 Collection of swimmers and the number of lanes.


protected override 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
      //Add in last partial heat
      //copy from array back into ArrayList
      //details on CDROM
}

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

Circle Seeding

The CircleSeeding class is derived from StraightSeeding, so it starts by calling the parent class’s seed method and then rearranges the top heats.


protected override void seed() {
      int circle;
      base.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);
             }
      }
}

Our Seeding Program

In this example, we took a list of swimmers from the Web who 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 9-2. In the top box, the 500 Free event is selected, and you can see that the swimmers are seeded in straight seeding from slowest to fastest. In the bottom box, the 100 Free event is selected and is circle seeded, with the last 3 heats seeded in a rotating fashion.

Figure 9-2. Straight seeding of the 500 Free and circle seeding of the 100 Free

image

Other Factories

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 simply creating the correct type of event when we read in the data. This code is in our init method of our form.


private void init() {
      //create array of events
      events = new ArrayList ();
      lsEvents.Items.Add ("500 Free");
      lsEvents.Items.Add ("100 Free");
      //and read in their data
      events.Add (new TimedFinalEvent ("500free.txt", 6));
      events.Add (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 with which we began the discussion.

When to Use a Factory Method

You should consider using a Factory method in the following situations.

• A class can’t anticipate which kind of class of objects 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 to recognize.

  1. The base class is abstract, and the pattern must return a complete working class.
  2. The base class contains default methods, and these methods are called unless 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 may do something quite different.

Thought Question

Seeding in track is carried out from inside to outside lanes. What classes would you need to develop to carry out tracklike seeding as well?

Program on the CD-ROM

image

..................Content has been hidden....................

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