Team LiB
Previous Section Next Section

The SRS Class Diagram, Revisited

Let's turn our attention back to the SRS class diagram that we produced in Part Two of the book. In speaking with our sponsors for the SRS system, we learn that they've decided to cut back on a few features in the interest of reducing development costs:

Therefore, we've pared down the SRS class diagram accordingly, to eliminate these unnecessary features; also, to keep the diagram from getting too cluttered, we didn't reflect field types or full method headers. The resultant diagram is shown in Figure 14-1.

Click To expand
Figure 14-1: The resultant streamlined SRS class diagram

Fortunately for us, the resultant model still provides examples of all of the key object-oriented elements that we need to learn how to program, as listed in Table 14-1.

Table 14-1: OO Features Illustrated by the SRS Class Diagram

OO Feature

Embodied in the SRS Class Diagram As Follows

Inheritance

The Person class serves as the base class for the Student and Professor classes.

Aggregation

We have two examples of this: the Transcript class represents an aggregation of TranscriptEntry objects, and the ScheduleOfClasses class represents an aggregation of Section objects.

One-to-one association

The maintains association between the Student and Transcript classes.

One-to-many association

The teaches association between Professor and Section; the offered as association between Course and Section.

Many-to-many association

The attends association between Student and Section; the prerequisite (reflexive) association between instances of the Course class.

Association Class

The TranscriptEntry class is affiliated with the attends association.

Reflexive association

The prerequisite association between instances of the Course class.

Abstract class

The Person class will be implemented as an abstract class.

Metadata

Each Course object embodies information that is relevant to multiple Section objects.

Static fields

Although not specifically illustrated in the class diagram, we'll take advantage of static fields when we code the Section class.

Static methods

Although not specifically illustrated in the class diagram, we'll take advantage of static methods when we code the TranscriptEntry class.

As mentioned earlier, we're going to implement a command-line driven version of the SRS in this chapter; in particular, we're going to code the eight classes illustrated in the class diagram along with a ninth "driver" class that will house the Main method necessary to run the application. In Chapter 16, we'll explain why it was useful to do so.

The Person Class (Specifying Abstract Classes)

The Person class will be the abstract base class for all of the "people" represented by the SRS. The derived classes of Person will include the Student and Professor classes.

Let's start by writing the code for the Person class (see Figure 14-2). We will be using the System.Console class in this class, so we'll include a using directive at the top of the code.


Figure 14-2: The Person class
using System;

The first thing that we notice in the class diagram is that the name of the class is italicized, which we learned in Chapter 10 means that Person is to be implemented as an abstract class. By including the keyword "abstract" in the class declaration, we prevent client code from ever being able to instantiate a Person object directly.

// We are making this class abstract because we do not wish for it
// to be instantiated.

public abstract class Person {

Person Fields

The Person class icon specifies two simple fields; we'll make all of our fields private throughout the SRS application unless otherwise stated:

  //------------
  // Fields.
  //------------

  private string name;
  private string ssn;

Person Constructors

We'll provide a constructor for the Person class that accepts two arguments, so as to initialize these two fields:

  //----------------
  // Constructor(s).
  //----------------

  // Initialize the field values using the set
  // accessor of the associated property.

  public Person(string name, string ssn) {
    this.Name = name;
    this.Ssn = ssn;
  }

Note that we use the set accessor of the Person class's properties to set the values of the name and ssn fields as was recommended in Chapter 13. Strictly speaking, the this. syntax isn't necessary when referencing a property declared in the same class, but can be included as a reminder that we're talking about a property associated with the current object.

And, because the creation of any constructor for a class suppresses the automatic generation of that class's default constructor as we discussed in Chapter 13, we'll program a parameterless constructor to replace it.

  // We're replacing the default constructor that got "wiped out"
  // as a result of having created a constructor previously. We reuse
  // the two-argument constructor with dummy values.
  public Person() : this("?", "???-??-????") {
    // Because this constructor needn't do anything more than what the parent's
    // constructor is going to do, the body of this constructor is empty.
  }

taking advantage of the : this() construct to reuse the code from the first constructor.

Person Properties

Next, we provide read/write properties for all of the fields, observing the proper property syntax as presented in Chapter 4:

  //-----------------
  // Properties.
  //-----------------

  public string Name {
    get {
      return name;
    }
    set {
      name = value;
    }
  }

  public string Ssn {
    get {
      return ssn;
    }
    set {
      ssn = value;
    }
  }

ToString Method

We'd like for all derived classes of the Person class to implement a ToString method, but we don't want to bother coding the details of such a method for Person; we'd prefer to let each derived class handle the details of how the ToString method will work in its own class-appropriate way. The best way to enforce the requirement for a ToString method is to declare ToString as an abstract method in Person, as we discussed in Chapters 7 and 13. The override keyword is required because the Object class (the base class of our Person class) also declares a ToString method.

  //-----------------------------
  // Miscellaneous other methods.
  //-----------------------------

  // We'll let each derived class implement how it wishes to be
  // represented as a String value.

  public abstract override string ToString();
Note?/td>

The ToString method is declared in the Object class as a nonabstract, virtual method. We're overriding that method in the Person class with an abstract method, but it's perfectly acceptable to do so, as we discussed in Chapter 13; making the Person ToString method abstract forces derived classes of Person to implement their own ToString logic.

Display Method

We also want all derived classes of Person to implement a Display method, to be used for printing the values of all of an object's fields to the command-line window; we'll be using the Display method solely for testing our application, to verify that an object's fields have been properly initialized. But, rather than making this method abstract as well, we'll go ahead and actually program the body of this method, since we know how we'd like the fields of Person to be displayed when these are inherited, at a minimum.

  // Used for testing purposes.

  public virtual void Display() {
    Console.WriteLine("Person Information:");
    Console.WriteLine("\tName: " + this.Name);
    Console.WriteLine("\tSoc. Security No.: " + this.Ssn);
  }

This way, we enable derived classes of Person (Student, Professor) to use the base keyword to recycle this logic in their own Display methods.

Note?/td>

Here is a "preview" excerpt from the Student class's Display method to illustrate how the base keyword will be used:

    public void Display() {
      // First, let's display the generic Person information inherited by Student.
      base.display();

      // Then, we'll go on to display the Student-specific attribute values.

Again, note that we're invoking the Person class's properties from within the WriteLine method calls, versus accessing Person fields directly by name.

That's all there is to programming the Person class—pretty straightforward! We'll tackle the Student and Professor classes derived from Person next.

The Student Class (Reuse Through Inheritance; Extending Abstract Classes; Delegation)

The Student class serves as an abstraction of a student, and is derived from the abstract Person class. In order to be able to instantiate Student objects within the SRS, we must implement all of Person's abstract methods concretely within the Student class, thus "breaking the spell" of abstractness, as we discussed in Chapter 7.

We indicate that Student is a derived class of Person (see Figure 14-3) with the following syntax:

Click To expand
Figure 14-3: The Student class
public class student : Person {

Student Fields

There are two fields indicated for the Student class in our class diagram—major and degree—but we learned in Chapter 10 that we must also encode associations as fields. Student participates in two associations:

  • attends, a many-to-many association with the Section class

  • maintains, a one-to-one association with the Transcript class

and so we must allow for each Student to maintain handles on one Transcript object and on many Section objects. Of the C# collection types that we learned about in Chapters 6 and 13Arrays, ArrayLists, and Hashtables—an ArrayList seems like the best choice for managing multiple Section handles:

  • An Array is a bit too rigid; we'd have to size the Array in advance to be large enough to accommodate references to all of the Sections that a Student will ever attend over the course of his or her studies at the university. An ArrayList, on the other hand, can start out small and automatically grow in size as needed.

  • The decision of whether to use an ArrayList versus a Hashtable to manage a collection comes down to whether or not we'll need to retrieve an object reference from the collection based on some key value. We don't anticipate the need for such a lookup capability as it pertains to the Sections that a Student has attended; we'll need the ability to verify if a Student has taken a particular Section or not, but this can be accomplished by using the ArrayList class's Contains method: that is, if attends is declared to be of type ArrayList, then we can use the statement

          if (attends.Contains(someSection)) { ... }
    

For most other uses of the attends collection, we'll need to step through the entire collection anyway, as when printing out the Student's course schedule. So, an ArrayList should serve our purposes just fine.

The fields for the Student class thus turn out as follows:

//------------
// Fields.
//------------
  private string major;
  private string degree;
  private Transcript transcript;
  private ArrayList attends; // of Sections

Student Constructors

We'll provide a constructor for convenience of initializing fields; note, though, that we aren't trying to initialize the attends ArrayList via the constructor, because a Student object will most likely come into existence before we know which Sections they will be attending. Nonetheless, we ideally prefer to instantiate collection fields such as the attends ArrayList so that we have an empty "egg carton" ready for us when it is time to add "eggs."

  //----------------
  // Constructor(s).
  //----------------

  // Reuse the code of the parent's constructor.
  // Initialize the field values using the set
  // accessor of the associated property.

  public Student(string name, string ssn,
                 string major, string degree) : base(name, ssn) {

    this.Major = major;
    this.Degree = degree;

    // Create a brand new Transcript.

    this.Transcript = new Transcript(this);

    // Note that we must instantiate an empty ArrayList.

    attends = new ArrayList();
}

In the preceding code, note that we create a brand-new Transcript object on the fly by calling the Transcript constructor, passing a reference to this Student in as the lone argument to the constructor. Since we haven't discussed the structure of the Transcript class yet, the signature of its constructor may seem a bit puzzling to you, but will make sense once we get a chance to review the Transcript class in its entirety later in this chapter.

We choose to overload the Student constructor by providing a second constructor signature, to be used if we wish to create a Student object for whom we don't yet know the major field of study or degree sought. We once again take advantage of the this keyword (introduced in Chapter 13) to reuse the code from the first constructor, passing in the String value "TBD" to serve as a temporary value for both the major and degree fields.

  // A second form of constructor, used when a Student has not yet
  // declared a major or degree.
  // Reuse the code of the other Student constructor.

  public Student(string name, string ssn) : this(name, ssn, "TBD", "TBD") {
    // Because this constructor needn't do anything more than what the parent's
    // constructor is going to do, the body of this constructor is empty.
  }

Student Properties

We provide properties for all of the simple (noncollection) fields:

  //-----------------
  // Properties.
  //-----------------

  public string Major {
    get {
      return major;
    }
    set {
      major = value;
    }
  }

  public string Degree {
    get {
      return degree;
    }
    set {
      degree = value;
    }
  }
  public Transcript Transcript {
    get {
      return transcript;
    }
    set {
      transcript = value;
    }
  }

The Transcript property associated with the transcript field returns a Transcript object reference; thus, the property's name is the same as its return type!

public Transcript Transcript {

Although this looks a bit strange, it does conform to the C# naming convention that the property name should match the field name, and the compiler won't have a problem with it. If we were to try to access the property with the following client code snippet:

Student s1 = new Student("Gerson Lopez", "123456789");
Transcipt t = new Transcript();
s1.Transcript = t;

the runtime would understand that the third line of code is accessing the get accessor of the Transcript property on the s1 Student object.

For the attends collection field, we'll provide methods AddSection and DropSection in lieu of traditional properties, to be used for adding and removing Section objects from the ArrayList; we'll talk about these methods momentarily.

Display Method

As we did for Person, we choose to provide a Display method for Student for use in testing our command-line version of the SRS. Because a Student is a Person, and because we've already gone to the trouble of programming a Display method for the fields inherited from Person, we'll reuse that method code by making use of the base keyword before going on to additionally display field values specific to a Student object:

  public override void Display() {
    // First, let's display the generic Person info.

    base.Display();

    // Then, display Student-specific info.
    Console.WriteLine("Student-Specific Information:");
    Console.WriteLine("\tMajor: " + this.Major);
    Console.WriteLine("\tDegree: " + this.Degree);
    DisplayCourseSchedule();
    PrintTranscript();
  }

Note that we're calling two of the Student class's other methods, DisplayCourseSchedule and PrintTranscript, from within the Student's Display method. We chose to program these as separate methods versus incorporating their code into the body of the Display method to keep the Display method from getting too cluttered.

PrintTranscript Method

The PrintTranscript method is a straightforward example of delegation: we use the Student's Transcript property to retrieve a handle on the Transcript object that belongs to this Student, and then invoke the Display method for that Transcript object:

  public void PrintTranscript() {
    this.Transcript.Display();
  }

Note that we could have accomplished this with two lines of code instead of one:

  public void PrintTranscript() {
    Transcript t = this.Transcript;
    t.Display();
  }

but as we discussed in Chapter 13, it's common to create expressions in C# by "chaining together" one method call after another. When each subsequent method is called in such an expression, it's called on the object reference that is returned by the previous method call in the chain; this is illustrated in Figure 14-4.

Click To expand
Figure 14-4: The "mechanics" of method chaining

There is no point in going to the trouble of declaring a variable t to serve as a handle on a Transcript object if we're only going to reference the variable one time, and then discard it when it goes out of scope as soon as the method exits.

DisplayCourseSchedule Method

The DisplayCourseSchedule method is a more complex example of delegation; we'll defer a discussion of this method until we've discussed a few more of the SRS classes.

ToString Method

By extending an abstract class, as we did with the Person class when we created the Student-derived class, we implicitly agree to implement any abstract method(s) specified by the parent class with concrete methods. In the case of the Person class, we have one such method, ToString:

  // We are forced to program this method because it is specified
  // as an abstract method in our parent class (Person); failing to
  // do so would render the Student class abstract, as well.
  //

  // For a Student, we wish to return a String as follows:
  //   Joe Blow (123-45-6789) [Master of Science - Math]

  public override string ToString() {
    return this.Name + " (" + this.Ssn + ") [" + this.Degree +
            " - " + this.Major + "]";
  }

AddSection Method

When a Student enrolls in a Section, this method will be used to deliver a handle on that Section object to the Student object so that it may be stored in the attends ArrayList:

  public void AddSection(Section s) {
    attends.Add(s);
  }

DropSection Method

When a Student withdraws from a Section, this method will be used to pass a handle on that Section object to the Student object, so that it can use the ArrayList class's Remove method to seek and remove that specific Section reference from the attends ArrayList:

  public void DropSection(Section s) {
    attends.Remove(s);
  }

IsEnrolledIn Method

This method is used to determine whether a given Student is already enrolled in a particular Section—that is, whether that Student is already maintaining a handle on the Section in question—by taking advantage of the ArrayList class's Contains method:

  public bool IsEnrolledIn(Section s) {
    if (attends.Contains(s)) {
      return true;
    }
    else {
      return false;
    }
  }

IsCurrentlyEnrolledInSimilar Method

Although not specified by our model, we've added another version of the IsEnrolledIn method called IsCurrentlyEnrolledInSimilar, because we found a need for such a method when we coded the Section class (coming up later in this chapter). No matter how much thought you put into object modeling, you'll inevitably determine the need for additional fields and methods for your classes once coding is under way, because coding causes you to think at a much more finely grained level of detail about the "mechanics" of your application.

Because this method is so complex, we'll show the method code in its entirety first, followed by an in-depth explanation.

  // Determine whether the Student is already enrolled in ANOTHER
  // Section of this SAME Course.

  public bool IsCurrentlyEnrolledInSimilar(Section s1) {
    bool foundMatch = false;
    Course c1 = s1.RepresentedCourse;
    IEnumerator e = GetEnrolledSections();
    while (e.MoveNext()) {
      Section s2 = (Section) e.Current;
      Course c2 = s2.RepresentedCourse;
      if (c1 == c2) {
        // There is indeed a Section in the attends
        // ArrayList representing the same Course.
        // Check to see if the Student is CURRENTLY
        // ENROLLED (i.e., whether or not he/she has
        // yet received a grade). If there is no
        // grade, he/she is currently enrolled; if
        // there is a grade, then he/she completed
        // the course some time in the past.
        if (s2.GetGrade(this) == null) {
          // No grade was assigned! This means
          // that the Student is currently
          // enrolled in a Section of this
          // same Course.
          foundMatch = true;
          break;
        }
      }
    }

    return foundMatch;
  }

In coding the Enroll method of the Section class, we realized that we needed a way to determine whether a particular Student is enrolled in any Section of a given Course. That is, if a Student is attempting to enroll for Math 101 Section 1, we want to reject this request if he or she is already enrolled in Math 101 Section 2. We chose to pass in a Section object reference as an argument to this method, although we could have alternatively declared the argument to this method to be a Course reference.

  // Determine whether the Student is already enrolled in another
  // Section of this same Course.

  public bool IsCurrentlyEnrolledInSimilar(Section s1) {

We initialize a flag to false, with the intention of resetting it to true later on if we do indeed discover that the Student is currently enrolled in a Section of the same Course.

    bool foundMatch = false;

We obtain a handle on the Course object that the Section of interest represents, and then step through an IEnumerator of all of the Sections that this Student is either currently enrolled in or has previously enrolled in, using the variable s2 to maintain a temporary handle on each such Section one by one.

    Course c1 = s1.RepresentedCourse;
    IEnumerator e = GetEnrolledSections();
    while (e.MoveNext()) {
      Section s2 = (Section) e.Current;

We obtain a handle on a second Course object—the Course object that Section s2 is a Section of—and test the equality of the two Course objects. If we find a match, we're not quite done yet, however, because the attends ArrayList for a Student holds onto all Sections that the Student has ever taken. To determine if Section s2 is truly a Section that the Student is currently enrolled in, we must check to see if a grade has been issued for this Section; a missing grade—that is, a grade value of null— indicates that the Section is currently in progress. As soon as we've found the first such situation, we can break out of the enclosing while loop and return a value of true to the caller.

    Course c2 = s2.RepresentedCourse;
    if (c1 == c2) {
      // There is indeed a Section in the attends
      // ArrayList representing the same Course.
      // Check to see if the Student is CURRENTLY
      // ENROLLED (i.e., whether or not he/she has
      // yet received a grade). If there is no
      // grade, he/she is currently enrolled; if
      // there is a grade, then he/she completed
      // the course some time in the past.
      if (s2.GetGrade(this) == null) {
          // No grade was assigned! This means
          // that the Student is currently
          // enrolled in a Section of this
          // same Course.
          foundMatch = true;
          break;
        }
      }
    }

    return foundMatch;

  } // end of method

GetEnrolledSections Method

The GetEnrolledSections method used previously is a simple one-liner:

  public IEnumerator GetEnrolledSections() {
    return attends.GetEnumerator();
  }

Recall from Chapter 13 that an IEnumerator instance can be used to step through the elements of a collection; hence, our choice of IEnumerator as the return type of this method. In this case, the collection holds the sections for which a student is registered.

Next, we'll turn our attention to the Professor class.

The Professor Class (Bidirectionality of Relationships)

The Professor class is a derived class of Person that serves as an abstraction a college professor. Because the code that is necessary to implement the Professor class is so similar to that of Student, we'll only comment on those features of Professor that are particularly noteworthy. We encourage you to look at the full code of the Professor class as downloaded from the Apress web site, however, to reinforce your ability to read and interpret C# syntax.

To indicate that the Professor is a derived class of Person (see Figure 14-5), we use the following syntax:


Figure 14-5: The Professor class
public class Professor : Person {

Professor Fields

The Professor class is involved in one association—the one-to-many teaches association with the Section class—and so we must provide a means for a Professor object to maintain multiple Section handles, which we do by creating a teaches field of type ArrayList:

  //------------
  // Fields.
  //------------

  private string title;
  private string department;
  private ArrayList teaches; // of Sections

AgreeToTeach Method

Our class diagram calls for us to implement an AgreeToTeach method. This method accepts a Section object reference as an argument, and begins by storing this handle in the teaches ArrayList:

  public void AgreeToTeach(Section s) {
    teaches.Add(s);

Associations, as modeled in a class diagram, are assumed to be bidirectional. When implementing associations in code, however, we must think about whether or not bidirectionality is important.

  • Can we think of any situations in which a Professor object would need to know which Sections it's responsible for teaching? Yes, as, for example, when we ask a Professor object to print out its teaching assignments.

  • How about the reverse: that is, can we think of any situations in which a Section object would need to know who is teaching it? Yes, as, for example, when we print out a Student's course schedule.

So, not only must we store a handle on the Section object in the Professor's teaches ArrayList, but we must also make sure that the Section object is somehow notified that this Professor is going to be its instructor. We accomplish this by accessing the Section object's Instructor property, passing it a handle on the Professor object whose method we are in the midst of executing:

  public void AgreeToTeach(Section s) {
    teaches.Add(s);

    // We need to link this bidirectionally.
    s.Instructor = this;
  }

We'll explore the implications of bidirectionality, and the various options for implementing bidirectional relationships, in more depth a bit later in this chapter.

We'll turn our attention next to the Course class.

The Course Class (Reflexive Relationships; Unidirectional Relationships)

The Course class, shown in Figure 14-6, represents an abstraction of a college course. Note that Course participates in the only reflexive association found in our UML diagram.

Click To expand
Figure 14-6: The Course class

Course Fields

Referring back to the SRS class diagram, we see that Course has three simple fields and participates in two associations:

  • offered as, a one-to-many association with the Section class, and

  • prerequisite, a many-to-many reflexive association

for a total of five fields:

  //------------
  // Fields.
  //------------

  private string courseNo;
  private string courseName;
  private double credits;
  private ArrayList offeredAsSection; // of Section object references
  private ArrayList prerequisites;    // of Course object references

Note that a reflexive association is handled in exactly the same way that any other association is handled: we provide the Course class with an ArrayList field called prerequisites that enables a given Course object to maintain handles on other Course objects. We've chosen not to encode this reflexive association bidirectionally. That is, a given Course object X knows which other Course objects A, B, C, etc. serve as its prerequisites, but doesn't know which Course objects L, M, N, etc. consider X to be one of their prerequisites (see Figure 14-7).

Click To expand
Figure 14-7: The prerequisite association isn't implemented bidirectionally.

Had we wanted this association to be bidirectional, we would have had to include a second ArrayList as a field in the Course class:

  private ArrayList prerequisites;  // of Course object references
  private ArrayList prerequisiteOf; // of Course object references

so that Course object X could hold onto this latter group of Course objects separately.

Course Methods

Most of the Course class methods use techniques that should already be familiar to you, based on our discussions of the Person, Professor, and Student classes. We'll highlight a few of the more interesting Course methods here, and leave it for you as an exercise to review the rest.

HasPrerequisites Method

This method inspects the size of the prerequisites ArrayList to determine whether or not a given Course has any prerequisite Courses:

  public bool HasPrerequisites() {
    if (prerequisites.Count > 0) {
      return true;
    }
    else {
      return false;
    }
  }

GetPrerequisites Method

This method returns an IEnumerator object as a convenient way for client code to step through the collection of prerequisite Course objects:

  public IEnumerator GetPrerequisites() {
    return prerequisites.GetEnumerator();
  }

We see this method in use within the Course Display method, and we'll also see it in use by the Section class a bit later on.

ScheduleSection Method

This method illustrates several interesting techniques. First, note that this method invokes the Section class constructor to fabricate a new Section object on the fly, storing one handle on this Section object in the offeredAsSection ArrayList before returning a second handle on the object to the client code:

  public Section ScheduleSection(char day, string time, string room,
                                 int capacity) {
    // Create a new Section (note the creative way in
    // which we are assigning a section number) ...
    Section s = new Section(offeredAsSection.Count + 1,
                            day, time, this, room, capacity);

    // ... and then remember it!
    offeredAsSection.Add(s);

    return s;
  }

Secondly, we're generating the first argument to the Section constructor—representing the Section number to be created—as a "one up" number by adding 1 to the number of elements in the offeredAsSection ArrayList. The first time that we invoke the ScheduleSection method for a given Course object, the ArrayList will be empty, and so the expression

  offeredAsSection.Count + 1

will evaluate to 1, and hence we'll be creating Section number 1. The second time that this method is invoked for the same Course object, the ArrayList will already contain a handle on the first Section object that was created, and so the expression

  offeredAsSection.Count + 1

will evaluate to 2, and hence we'll be creating Section number 2, and so forth.

There is one flaw with this approach: if we were to create, then delete, Section objects, the size of the ArrayList would expand and contract, and we could wind up with duplicate Section numbers. We'll remedy this flaw in Chapter 15.

Now, let's turn our attention to the Section class.

The Section Class (Representing Association Classes; Public Constant Fields)

The Section class represents an abstraction of a particular section of a college course as offered in a given semester, and is illustrated in Figure 14-8.

Click To expand
Figure 14-8: The Section class

Section Fields

The Section class participates in numerous relationships with other classes:

  • offered as, a one-to-many association with Course

  • An unnamed, one-to-many aggregation with ScheduleOfClasses

  • teaches, a one-to-many association with Professor

  • attends, a many-to-many association with Student

The attends association is in turn affiliated with an association class, TranscriptEntry. We learned in Chapter 10 that an association class can alternatively be depicted in a class diagram as having direct relationships with the classes at either end of the association, as shown in Figure 14-9, and so we'll encode a fifth relationship for the Section class, namely

  • assigns grade, a one-to-many association with the TranscriptEntry class

Click To expand
Figure 14-9: Adding the assigns grade association

(You may be wondering whether we should now go back and adjust the Student class to reflect the earns grade association with the TranscriptEntry class as a Student class field. The decision of whether or not to implement a particular relationship in code depends in part on what we anticipate our usage patterns to be, as we discussed in Chapter 10. We'll defer the decision of what to do with earns grade until we talk about the TranscriptEntry class in a bit more depth later in this chapter.)

We'll represent these five relationships in terms of Section fields as follows.

A Section object need only maintain a handle on one other object for those one-to-many relationships in which Section occupies the "many" end, namely

  private Course representedCourse;
  private ScheduleOfClasses offeredIn;
  private Professor instructor;

For the two situations in which Section needs to maintain handles on collections of objects—Students and TranscriptEntries—we're going to employ Hashtables instead of ArrayLists this time. We do so because it's conceivable that we'll have a frequent need to "pluck" a given item from the collection directly, and Hashtable provides a key-based lookup mechanism that is ideal for this purpose.

For the Hashtable of Student object references, we'll use a string representing the Student's social security number (ssn) as a key for looking up a Student:

  // The enrolledStudents Hashtable stores Student object references,
  // using each Student's ssn as a string key.

  private Hashtable enrolledStudents;

but for the Hashtable of TranscriptEntry object references, on the other hand, we'll use a Student object as a whole as a key for looking up that particular Student's TranscriptEntry as issued by this Section.

  // The assignedGrades Hashtable stores TranscriptEntry object
  // references, using a reference to the Student to whom it belongs
  // as the key.

  private Hashtable assignedGrades;

Public Constant Fields

In the Section class, we encounter our first use of const fields, which as we learned in Chapter 13 are an excellent means of defining constant values. In this particular situation, we want to define some status codes that the Section class can use when signaling the outcome of an enrollment attempt.

  public const int SUCCESSFULLY_ENROLLED = 0;
  public const int SECTION_FULL = 1;
  public const int PREREQ_NOT_SATISFIED = 2;
  public const int PREVIOUSLY_ENROLLED = 3;

Let's look at how these are put to use by studying the Enroll method of Section.

Enroll Method

This is a very complex method; we'll present the code in its entirety first without discussing it, and will then proceed to "dissect" it afterward.


  public int Enroll(Student s) {
    // First, make sure that this Student is not already
    // enrolled for this Section, has not already enrolled
    // in another section of this class and that he/she has
    // NEVER taken and passed the course before.

    Transcript transcript = s.Transcript;

    if (s.IsEnrolledIn(this) ||
        s.IsCurrentlyEnrolledInSimilar(this) ||
        transcript.VerifyCompletion(this.RepresentedCourse)) {
      return PREVIOUSLY_ENROLLED;
    }

    // If there are any prerequisites for this course,
    // check to ensure that the Student has completed them.

    Course c = this.RepresentedCourse;
    if (c.HasPrerequisites()) {
      IEnumerator e = c.GetPrerequisites();
      while (e.MoveNext()) {
        Course pre = (Course) e.Current;

        // See if the Student's Transcript reflects
        // successful completion of the prerequisite.

        if (!transcript.VerifyCompletion(pre)) {
          return PREREQ_NOT_SATISFIED;
        }
      }
    }

    // If the total enrollment is already at the
    // the capacity for this Section, we reject this
    // enrollment request.

    if (!ConfirmSeatAvailability()) {
      return SECTION_FULL;
    }

    // If we made it to here in the code, we're ready to
    // officially enroll the Student.

    // Note bidirectionality: this Section holds
    // onto the Student via the Hashtable, and then
    // the Student is given a handle on this Section.

    enrolledStudents.Add(s.Ssn, s);
    s.AddSection(this);
    return SUCCESSFULLY_ENROLLED;
  }

We begin by verifying that the Student seeking enrollment (represented by argument s) hasn't already enrolled for this Section, and furthermore that he or she has never taken and successfully completed this Course (any Sections) in the past. To do so, we must obtain a handle on the Student's transcript; we store it in a locally declared reference variable called transcript, because we're going to need to consult with the Transcript object twice in this method:

  public int Enroll(Student s) {
    // First, make sure that this Student is not already
    // enrolled for this Section, has not already enrolled
    // in another section of this class, and that he/she has
    // NEVER taken and passed the course before.

    Transcript transcript = s.Transcript;

We then use an if statement to test for either of two conditions: (a) is the Student currently enrolled in this Section or another Section of the same Course, and/or (b) does his or her Transcript indicate successful prior completion of the Course that is represented by this Section? Because we only need to use this Course object once in this method, we don't bother to save the handle returned to us by the RepresentedCourse property in a local variable; we just nest the invocation of this method within the call to VerifyCompletion, so that the Course object can be retrieved by the former and immediately passed along as an argument to the latter.

    if (s.IsEnrolledIn(this) ||
        s.IsCurrentlyEnrolledInSimilar(this) ||
        transcript.VerifyCompletion(this.RepresentedCourse)) {
      return PREVIOUSLY_ENROLLED;
    }

Whenever we encounter a return statement midway through a method as we have here, the method will immediately terminate execution without running to completion.

Note our use of PREVIOUSLY_ENROLLED, one of the constant fields declared by Section, as a return value. Declaring and using such standardized values is a great way to communicate status back to client code, as this sample pseudocode invocation of the Enroll method illustrates:

    // Client code (pseudocode).

    Section sec = new Section(...);
    Student s = new Student(...);
    // ...
    int status = sec1.Enroll(s1);
    if (status == Section.PREVIOUSLY_ENROLLED) {
      // Pseudocode.
      take appropriate action
    }
    if (status == Section.PREREQ_NOT_SATISFIED) {
      // Pseudocode.
      take appropriate action
    }
    else if (status == Section.SECTION_FULL) {
      // Pseudocode.
      take appropriate action
    }
    // etc.

Thus, both the client code and the Section object's method code are using the same symbolic names to communicate.

Next, we check to see if the Student has satisfied the prerequisites for this Section, if there are any. We use the Section's RepresentedCourse property to obtain a handle on the Course object that this Section represents, and then invoke the HasPrerequisites method on that Course object; if the result returned is true, then we know that there are prerequisites to be checked.

    // If there are any prerequisites for this course,
    // check to ensure that the Student has completed them.

    Course c = this.RepresentedCourse;
    if (c.HasPrerequisites()) {

If there are indeed prerequisites for this Course, we use the GetPrerequisites method defined by the Course class to obtain an IEnumerator object of all prerequisite Courses.

    IEnumerator e = c.GetPrerequisites();

Then, we iterate through the IEnumerator; for each Course object reference pre that we extract from the IEnumerator, we invoke the VerifyCompletion method on the Student's Transcript object, passing in the prerequisite Course object reference pre. We haven't taken a look at the inner workings of the Transcript class yet, so for now, all we need to know about VerifyCompletion is that it will return a value of true if the Student has indeed successfully taken and passed the Course in question, or a value of false otherwise. We want to take action in situations where a prerequisite was not satisfied, so we use the unary negation operator (!) in front of the expression to indicate that we want the if test to succeed if the method call returns a value of false:

      while (e.MoveNext()) {
        Course pre = (Course) e.Current;

        // See if the Student's Transcript reflects
        // successful completion of the prerequisite.

        if (!transcript.VerifyCompletion(pre)) {
          return PREREQ_NOT_SATISFIED;
        }
      } // end if

If we make it through the prerequisite check without triggering the return statement, the next step in this method is to verify that there is still available seating in the Section; we return the status value SECTION_FULL value if there is not.

    // If the total enrollment is already at the
    // the capacity for this Section, we reject this
    // enrollment request.

    if (!ConfirmSeatAvailability()) {
      return SECTION_FULL;
    }

Finally, if we've made it through both of the preceding tests unscathed, we're ready to officially enroll the Student. We use the Hashtable class's Add method to insert the Student reference into the enrolledStudents Hashtable, accessing the Ssn property on the Student to retrieve the string value of its ssn field, which we pass in as the key value. To achieve bidirectionality of the link between a Student and a Section, we then turn around and invoke the AddSection method on the Student object reference, passing it a handle on this Section.


    // Note bidirectionality: this Section holds
    // onto the Student via the Hashtable, and then
    // the Student is given a handle on this Section.

    enrolledStudents.Add(s.Ssn, s);
    s.AddSection(this);
    return SUCCESSFULLY_ENROLLED;
  }

Drop Method

The Drop method of Section performs the reverse operation of the Enroll method. We start by verifying that the Student in question is indeed enrolled in this Section, since we can't drop a Student who isn't enrolled in the first place:

  public bool Drop(Student s) {
    // We may only drop a student if he/she is enrolled.

    if (!s.IsEnrolledIn(this)) {
      return false;
    }

and, if he or she truly is enrolled, then we use the Hashtable class's Remove method to locate and delete the Student reference, again via its ssn field value. In the interest of bidirectionality, we invoke the DropSection method on the Student, as well, to get rid of the handles at both ends of the link.

    else {
      // Find the student in our Hashtable, and remove it.

      enrolledStudents.Remove(s.Ssn);

      // Note bidirectionality.

      s.DropSection(this);
      return true;
    }
  }

PostGrade Method

The PostGrade method is used to assign a grade to a Student by creating a TranscriptEntry object to link the two. To ensure that we aren't inadvertently trying to assign a grade to a given Student more than once, we first check the assignedGrades Hashtable to see if it already contains an entry for this Student. If the Hashtable search returns anything but null, then we know a grade has already been posted for this Student, and we terminate execution of the method.

  public bool PostGrade(Student s, string grade) {
    // Make sure that we haven't previously assigned a
    // grade to this Student by looking in the Hashtable
    // for an entry using this Student as the key. If
    // we discover that a grade has already been assigned,
    // we return a value of false to indicate that
    // we are at risk of overwriting an existing grade.
    // (A different method, EraseGrade, can then be written
    // to allow a Professor to change his/her mind.)

    if (assignedGrades[s] != null) {
      return false;
    }

Assuming that a grade wasn't previously assigned, we invoke the appropriate constructor to create a new TranscriptEntry object. As we'll see when we study the inner workings of the TranscriptEntry class, this object will maintain handles on both the Student to whom a grade has been assigned and on the Section for which the grade was assigned. To enable this latter link to be bidirectional, we also store a handle on the TranscriptEntry object in the Section's Hashtable for this purpose.

    // First, we create a new TranscriptEntry object. Note
    // that we are passing in a reference to THIS Section,
    // because we want the TranscriptEntry object,
    // as an association class ..., to maintain
    // "handles" on the Section as well as on the Student.
    // (We'll let the TranscriptEntry constructor take care of
    // "hooking" this T.E. to the correct Transcript.)

    TranscriptEntry te = new TranscriptEntry(s, grade, this);

    // Then, we "remember" this grade because we wish for
    // the connection between a T.E. and a Section to be
    // bidirectional.

    assignedGrades.Add(s, te);

    return true;
  }

ConfirmAvailability Method

The ConfirmSeatAvailability method called from within Enroll is an internal housekeeping method; by declaring it to have private versus public visibility, we restrict its use so that only other methods of the Section class may invoke it.

  private bool ConfirmSeatAvailability() {
    if (enrolledStudents.Count < GetSeatingCapacity()) {
      return true;
    }
    else {
      return false;
    }
  }

Delegation, Revisited

In discussing the Student class, we briefly mentioned the DisplayCourseSchedule method as a complex example of delegation, and promised to come back and discuss it further.

What are the "raw materials"—data—available for an object to use when it's responding to a service request by executing one of its methods? By way of review, an object has at its disposal the following data sources:

  • Simple data and/or object references that have been encapsulated as fields within the object itself

  • Simple data and/or object references that are passed in as arguments in the method signature

  • Data that is made available globally to the application as public static fields of some other class (We saw this technique demonstrated earlier in the chapter when we defined various status codes for the Section class, and will see this technique used again several more times within the SRS.)

  • Data that can be requested from any of the objects that this object has a handle on, a process that we learned in Chapter 3 is known as delegation

It's this last source of data—data available by collaborating with other objects and delegating part of a task to them—that is going to play a particularly significant role in implementing the DisplayCourseSchedule method for the Student class.

Let's say we want the DisplayCourseSchedule method to display the following information for each Section that a Student is currently enrolled in:

  Course No.:
  Section No.:
  Course Name:
  Meeting Day and Time:
  Room Location:
  Professor's Name:

For example:

Course Schedule for Fred Schnurd
  Course No.: CMP101
  Section No.: 2
  Course Name: Beginning Computer Technology
  Meeting Day and Time Held: W - 6:10 - 8:00 PM
  Room Location: GOVT202
  Professor's Name: John Carson
  -----
  Course No.: ART101
  Section No.: 1
  Course Name: Beginning Basketweaving
  Meeting Day and Time Held: M - 4:10 - 6:00 PM
  Room Location: ARTS25
  Professor's Name: Snidely Whiplash
  -----

Let's start by looking at the fields of the Student class, to see which of this information is readily available to us. Student inherits from Person:

private string name;
private string ssn;

and adds

private string major;
private string degree;
private Transcript transcript;
private ArrayList attends; // of Sections

Let's begin to write the method; by stepping through the attends ArrayList, we can gain access to Section objects one by one:


  public void DisplayCourseSchedule() {
    // Display a title first.

    Console.WriteLine("Course Schedule for " + this.Name);

    // Step through the ArrayList of Section objects,
    // processing these one by one.

    for (int i = 0; i < attends.Count; i++) {
      Section s = (Section) attends[i];

      // Now what goes here????
      // We must create the rest of the method ...
    }
  }

Now that we have the beginnings of the method, let's determine how to fill in the gap (highlighted) in the preceding code.

Looking at all of the method and property headers declared for the Section class as evidence of the services that a Section object can perform, we see that several of these can immediately provide us with useful pieces of information relative to our mission of displaying a Student's course schedule:

Properties:

  public int SectionNo(get; set;)
  public char DayOfWeek(get; set;)
  public string TimeOfDay(get; set;)
  public Professor Instructor(get; set;)
  public Course RepresentedCourse(get; set;)
  public string Room(get; set;)
  public int SeatingCapacity(get; set;)
  public ScheduleOfClasses OfferedIn(get; set;)

Methods:

  public override string ToString()
  public int Enroll(Student s)
  public bool Drop(Student s)
  public int GetTotalEnrollment()
  public override void Display()
  public void DisplayStudentRoster()
  public string GetGrade(Student s)
  public bool PostGrade(Student s, string grade)
  public bool SuccessfulCompletion(Student s)
  public bool IsSectionOf(Course c)

Let's put the four highlighted Section class properties to use, and where we can't yet fill the gap completely, we'll insert "???" as a placeholder:

  public void DisplayCourseSchedule() {
    // Display a title first.

    Console.WriteLine("Course Schedule for " + this.Name);

    // Step through the ArrayList of Section objects,
    // processing these one by one.

    for (int i = 0; i < attends.Count; i++) {
      Section s = (Section) attends[i];

      // Since the attends ArrayList contains Sections that the
      // Student took in the past as well as those for which
      // the Student is currently enrolled, we only want to
      // report on those for which a grade has not yet been
      // assigned.

      if (s.GetGrade(this) == null) {
        Console.WriteLine("\tCourse No.: " +???);
        Console.WriteLine("\tSection No.: " + s.SectionNo);
        Console.WriteLine("\tCourse Name: " +???);
        Console.WriteLine("\tMeeting Day and Time Held: " +
                s.DayOfWeek + " - " + s.TimeOfDay);
        Console.WriteLine("\tRoom Location: " + s.Room);
        Console.WriteLine("\tProfessor's Name: " +???);
        Console.WriteLine("\t-----");
      }
    }
  }

Now what about the remaining "holes"?

The following two Section class properties:

public Professor Instructor(get; set;)
public Course RepresentedCourse(get; set;)

will each hand us yet another object that we can "talk to": the Professor who teaches this Section, and the Course that this Section represents. Let's now look at what these objects can perform in the way of services.

A Professor object provides the following properties/methods (those marked with an asterisk comment (// *) are inherited from Person):


    public string Name(get; set;)       // *
    public string Ssn(get; set;)           // *
    public string Title(get; set;)
    public string Department(get; set;)
    public override void Display()
    public override string ToString()
    public void DisplayTeachingAssignments()
    public void AgreeToTeach(Section s)

and a Course object provides the following properties/methods:

    public string CourseNo(get; set;)
    public string CourseName(get; set;)
    public double Credits(get; set;)
    public void Display()
    public override string ToString()
    public void AddPrerequisite(Course c)
    public bool HasPrerequisites()
    public IEnumerator GetPrerequisites()
    public Section ScheduleSection(char day, string time, string room,
      int capacity)

If we bring all of the highlighted properties to bear, we can wrap up the DisplayCourseSchedule method of the Student class as follows:

  public void DisplayCourseSchedule() {
    // Display a title first.

    Console.WriteLine("Course Schedule for " + this.Name);

    // Step through the ArrayList of Section objects,
    // processing these one by one.

    for (int i = 0; i < attends.Count; i++) {
      Section s = (Section) attends[i];

      // Since the attends ArrayList contains Sections that the
      // Student took in the past as well as those for which
      // the Student is currently enrolled, we only want to
      // report on those for which a grade has not yet been
      // assigned.
      if (s.GetGrade(this) == null) {
        Console.WriteLine("\tCourse No.: " +
                             s.RepresentedCourse.CourseNo);
        Console.WriteLine("\tSection No.: " + s.SectionNo);
        Console.WriteLine("\tCourse Name: " +
                             s.RepresentedCourse.CourseName);
        Console.WriteLine("\tMeeting Day and Time Held: " +
                            s.DayOfWeek + " - " +
                            s.TimeOfDay);
        Console.WriteLine("\tRoom Location: " + s.Room);
        Console.WriteLine("\tProfessor's Name: " +
                             s.Instructor.Name);
        Console.WriteLine("\t-----");
      }
    }
  }

Note that we're accessing the RepresentedCourse property of each Section object twice; we could instead access this property once, retrieving a Course object reference and holding on to it with reference variable c, as shown in the next snippet (and, while we're at it, we'll retrieve the Professor object representing the instructor for this section, and will hold onto with reference variable p):

    for (int i = 0; i < attends.Count; i++) {
      Section s = (Section) attends[i];
      Course c = s.RepresentedCourse;
      Professor p = s.Instructor;

      // ...

      if (s.GetGrade(this) == null) {
        Console.WriteLine("\tCourse No.: " + c.CourseNo);
        Console.WriteLine("\tSection No.: " + s.SectionNo);
        Console.WriteLine("\tCourse Name: " + c.CourseName);
        Console.WriteLine("\tMeeting Day and Time Held: " +
                            s.DayOfWeek + " - " +
                            s.TimeOfDay);
        Console.WriteLine("\tRoom Location: " + s.Room);
        Console.WriteLine("\tProfessor's Name: " + p.Name);
        Console.WriteLine("\t-----");
      }
    }
  }

This method is a classic example of delegation:

  • We start out asking a Student object to do something for us—namely, to display the Student's course schedule.

  • The Student object in turn has to talk to the Section objects representing sections that the student is enrolled in, asking each of them to perform some of their services (methods).

  • The Student object also has to ask those Section objects to hand over references to the Professor and Course objects that the Section objects know about, in turn asking those objects to perform some of their services.

This multitiered collaboration is depicted conceptually in Figure 14-10.

Click To expand
Figure 14-10: Many objects collaborate to accomplish a single method.

The ScheduleOfClasses Class

The ScheduleOfClasses class is a fairly simple class that serves as an example of how we can encapsulate a "standard" collection object within another class (see Figure 14-11), a design technique that we discussed in Chapter 6.


Figure 14-11: The ScheduleOfClasses class

ScheduleOfClasses Fields

The ScheduleOfClasses class consists of only two fields: a simple string representing the semester for which the schedule is valid (for example, "SP2004" for the Spring 2004 semester), and a Hashtable used to maintain handles on all of the Sections that are being offered that semester.

  private string semester;

  // This Hashtable stores Section object references, using
  // a String concatenation of course no. and section no. as the
  // key, for example, "MATH101 - 1".

  private Hashtable sectionsOffered;

AddSection Method

Aside from a simple constructor, a Display method, and a property for the semester field, the only other feature that this class provides is a method for adding a Section object to the Hashtable, and then bidirectionally connecting the ScheduleOfClasses object back to the Section:

  public void AddSection(Section s) {
    // We formulate a key by concatenating the course no.
    // and section no., separated by a hyphen.

    string key = s.RepresentedCourse.CourseNo +
                  " - " + s.SectionNo;
    sectionsOffered.Add(key, s);

    // Bidirectionally hook the ScheduleOfClasses back to the Section.

    s.OfferedIn = this;
  }

The TranscriptEntry Association Class (Static Methods)

As we discussed in Chapter 6, the TranscriptEntry class (depicted in Figure 14-12) represents a single line item on a student's transcript.


Figure 14-12: The TranscriptEntry class

TranscriptEntry Fields

As we saw earlier in this chapter, the TranscriptEntry class has one simple field, grade, and maintains associations with three other classes (see Figure 14-13):

  • earns grade, a one-to-many association with Student

  • assigns grade, a one-to-many association with Section

  • An unnamed, one-to-many aggregation with the Transcript class

Click To expand
Figure 14-13: The TranscriptEntry class has relationships with many other classes.

TranscriptEntry is at the "many" end of all of these associations, and so it only needs to maintain a single handle on each type of object; no collection fields are required:

  private string grade;
  private Student student;
  private Section section;
  private Transcript transcript;

TranscriptEntry Constructor

The constructor for this class does most of the work of maintaining all of these relationships.

Using the set accessor of the Student property, we store the associated Student object's handle in the appropriate field.

  //----------------
  // Constructor(s).
  //----------------

  // Initialize the field values using the set accessor of the
  // associated property.

  public TranscriptEntry(Student s, string grade, Section se) {
    this.Student = s;

Note that we have chosen not to maintain the earns grade association bidirectionally; that is, we've provided no code in either this or the Student class to provide the Student object with a handle on this TranscriptEntry object. We've made this decision based upon the fact that we don't expect a Student to ever have to manipulate TranscriptEntry objects directly. Every Student object has an indirect means of reaching all of its TranscriptEntry objects, via the handle that a Student object maintains on its Transcript object, and the handles that the Transcript object in turn maintains on its TranscriptEntry objects. One might think that giving a Student object the ability to directly pull a given TranscriptEntry might be useful when wishing to determine the grade that the Student earned for a particular Section, but we've provided an alternative means of doing so, via the Section class's GetGrade method.

Even though it may not appear so, we are maintaining the assigns grade association with Section bidirectionally. We only see half of the "handshake" in the TranscriptEntry constructor:

    this.Section = se;

but recall that when we looked at the PostGrade method of the Section class, we discussed the fact that Section was responsible for maintaining the bidirectionality of this association. When the Section's PostGrade method invokes the TranscriptEntry constructor, the Section object is returned a handle on this TranscriptEntry object, which it stores in the appropriate field. So, we only need worry about the second half of this "handshake" in TranscriptEntry.

On the other hand, the TranscriptEntry object has full responsibility for maintaining the bidirectionality of the association between it and the Transcript object:

    // Obtain the Student's transcript ...

    Transcript t = s.Transcript;

    // ... and then hook the Transcript and the TranscriptEntry
    // together bidirectionally.

    this.Transcript = t;
    t.AddTranscriptEntry(this);
  }

ValidateGrade and PassingGrade Methods

The TranscriptEntry class provides our first SRS example of public static methods: it declares two methods, ValidateGrade and PassingGrade, that may be invoked as utility methods on the TranscriptEntry class from anywhere in the SRS application.

The first method is used to validate whether or not a particular string—say, "B+"—is formatted properly to represent a grade: namely, whether it starts with a capital letter "A," "B," "C," "D," "F," or "I" (for "Incomplete"), followed by an optional + or - character:

  // These next two methods are declared to be static, so that they
  // may be used as utility methods.
  public static bool ValidateGrade(string grade) {
    bool outcome = false;

    if (grade.Equals("F") || grade.Equals("I")) {
      outcome = true;
    }

    if (grade.StartsWith("A") || grade.StartsWith("B") ||
        grade.StartsWith("C") || grade.StartsWith("D")) {
      if (grade.Length == 1) {
        outcome = true;
      }
        else {
          if (grade.Length > 2) {
            outcome = false;
          }
          else {
            if (grade.EndsWith("+") || grade.EndsWith("-")) {
              outcome = true;
          }
          else {
            outcome = false;
          }
        }
      }
    }

    return outcome;
  }

The second method is used to determine whether or not a particular string—say "D+"—represents a passing grade.

  public static bool PassingGrade(string grade) {
    // First, make sure it is a valid grade.

    if (!ValidateGrade(grade)) {
      return false;
    }

    // Next, make sure that the grade is a D or better.

    if (grade.StartsWith("A") || grade.StartsWith("B") ||
        grade.StartsWith("C") || grade.StartsWith("D")) {
      return true;
    }
    else {
      return false;
    }
  }

As we discussed in Chapters 7 and 13, public static methods are invoked on the hosting class as a whole—in other words, an object needn't be instantiated in order to use these methods.

We'll see actual use of the PassingGrade method in a moment, when we discuss the Transcript class.

The Transcript Class

The Transcript class, shown in Figure 14-14, serves as an abstraction of a student's transcript; i.e., a collection of TranscriptEntry references.

Click To expand
Figure 14-14: The Transcript class

Transcript Fields

The Transcript class participates in two relationships:

  • maintains, a one-to-one association with Student

  • An unnamed, one-to-many aggregation with TranscriptEntry

The SRS class diagram doesn't call out any other fields for the Transcript class, so we only encode these two:

  private ArrayList transcriptEntries; // of TranscriptEntry object references
  private Student studentOwner;

VerifyCompletion Method

The Transcript class has one particularly interesting method, VerifyCompletion, which is used to determine whether or not the Transcript contains evidence that a particular Course requirement has been satisfied. This method steps through the ArrayList of TranscriptEntries maintained by the Transcript object:

  public bool VerifyCompletion(Course c) {
    bool outcome = false;

    // Step through all TranscriptEntries, looking for one
    // that reflects a Section of the Course of interest.

    for (int i = 0; i < transcriptEntries.Count; i++) {
      TranscriptEntry te = (TranscriptEntry) transcriptEntries[i];

For each entry, the method obtains a handle on the Section object represented by this entry, and then invokes the IsSectionOf method on that object to determine whether or not that Section represents the Course of interest.

      Section s = te.Section;

      if (s.IsSectionOf(c)) {

Assuming that the Section is indeed relevant, the method next uses the static PassingGrade method of the TranscriptEntry class to determine whether the grade earned in this Section was a passing grade or not. If it was a passing grade, we can terminate the loop immediately, since we only need to find one example of a passing grade for the Course of interest in order to ensure that the student whose Transcript we are inspecting contains evidence of successful course completion.

        // Ensure that the grade was high enough.

        if (TranscriptEntry.PassingGrade(te.Grade)) {
          outcome = true;

          // We've found one, so we can afford to
          // terminate the loop now.

          break;
        }
      }
    }

    return outcome;
  }

The SRS Driver Program

Now that we've coded all of the classes called for by our model of the SRS, we need a way to test these. We could wait to put our application through its paces until we've built a GUI front-end; however, it would be nice to know sooner rather than later that our core classes are working properly. One very helpful technique for doing so is to write a command-line–driven program to instantiate objects of varying types and to invoke their critical methods, displaying the results to the command-line window for us to inspect.

We've developed just such a program by creating a class called SRS with a Main method that will serve as our test driver.

Public Static Fields

We're going to instantiate some Professor, Student, Course, and Section objects in this program, so we need a way to organize handles on these objects; we'll create collection objects as fields of the SRS class to hold each of these different object types. While we're at it, we'll declare them to be public static fields, which means that we're making these main object collections globally available to the entire application.

  // We can effectively create "global" data by declaring
  // public static fields in the main class.

  // Entry points/"roots" for getting at objects.

  public static ScheduleOfClasses scheduleOfClasses =
    new ScheduleOfClasses("SP2004");
  public static ArrayList faculty; // of Professors
  public static ArrayList studentBody; // of Students
  public static ArrayList courseCatalog; // of Courses

The SRS ScheduleOfClasses class serves as a collection point for Section objects; for the other types of objects, we use simple ArrayLists, although we could go ahead and design classes comparable to ScheduleOfClasses to serve as encapsulated collections, perhaps named Faculty, StudentBody, and CourseCatalog, respectively. (In fact, we'll actually do so in Chapter 15, for reasons that will become clear at that time.) We don't need a collection for Transcript objects—we'll get to these via the handles that Student objects maintain—nor do we need one for TranscriptEntry objects—we'll get to these via the Transcript objects themselves.

Main Method

We'll now dive into the Main method for the SRS class. We'll start by declaring reference variables for each of the four main object types:

static void Main(string[] args) {
  Professor p1, p2, p3;
  Student s1, s2, s3;
  Course c1, c2, c3, c4, c5;
  Section sec1, sec2, sec3, sec4, sec5, sec6, sec7;

and we'll then use their various constructors to fabricate object instances, storing handles in the appropriate collections. (In Chapter 15, we'll explore how we can instantiate objects by reading data from a file instead of "hard coding" field values as we have here.)


  // -----------
  // Professors.
  // -----------

  p1 = new Professor("Jacquie Barker", "123-45-6789",
                     "Adjunct Professor", "Information Technology");
  p2 = new Professor("John Carson", "567-81-2345",
                     "Full Professor", "Information Technology");
  p3 = new Professor("Jackie Chan", "987-65-4321",
                     "Full Professor", "Information Technology");

  // Add these to the appropriate ArrayList.

  faculty = new ArrayList();
  faculty.Add(p1);
  faculty.Add(p2);
  faculty.Add(p3);

  // ---------
  // Students.
  // ---------

  s1 = new Student("Joe Blow", "111-11-1111", "Math", "M.S");
  s2 = new Student("Gerson Lopez", "222-22-2222",
                   "Information Technology", "Ph. D");
  s3 = new Student("Mary Smith", "333-33-3333", "Physics", "B.S");

  // Add these to the appropriate ArrayList.

  studentBody = new ArrayList();
  studentBody.Add(s1);
  studentBody.Add(s2);
  studentBody.Add(s3);

  // --------
  // Courses.
  // --------

  c1 = new Course("CMP101","Beginning Computer Technology", 3.0);
  c2 = new Course("OBJ101","Object Methods for Software Development", 3.0);
  c3 = new Course("CMP283","Higher Level Languages (C#)", 3.0);
  c4 = new Course("CMP999","Living Brain Computers", 3.0);
  c5 = new Course("ART101","Beginning Basketweaving", 3.0);
  // Add these to the appropriate ArrayList.

  courseCatalog = new ArrayList();
  courseCatalog.Add(c1);
  courseCatalog.Add(c2);
  courseCatalog.Add(c3);
  courseCatalog.Add(c4);
  courseCatalog.Add(c5);

We use the AddPrerequisite method of the Course class to interrelate some of the Courses, so that c1 is a prerequisite for c2, c2 for c3, and c3 for c4. The only Courses that we don't specify prerequisites for in our test case are c1 and c5.

  // Establish some prerequisites (c1 => c2 => c3 => c4).

  c2.AddPrerequisite(c1);
  c3.AddPrerequisite(c2);
  c4.AddPrerequisite(c3);

To create Section objects, we take advantage of the Course class's ScheduleSection method, which, as you may recall, contains an embedded call to a Section class constructor. Each invocation of ScheduleSection returns a handle to a newly created Section object, which we store in the appropriate collection.

  // ---------
  // Sections.
  // ---------

  // Schedule sections of each Course by calling the
  // ScheduleSection method of Course (which internally
  // invokes the Section constructor).

  sec1 = c1.ScheduleSection('M', "8:10 - 10:00 PM", "GOVT101", 30);
  sec2 = c1.ScheduleSection('W', "6:10 - 8:00 PM", "GOVT202", 30);
  sec3 = c2.ScheduleSection('R', "4:10 - 6:00 PM", "GOVT105", 25);
  sec4 = c2.ScheduleSection('T', "6:10 - 8:00 PM", "SCI330", 25);
  sec5 = c3.ScheduleSection('M', "6:10 - 8:00 PM", "GOVT101", 20);
  sec6 = c4.ScheduleSection('R', "4:10 - 6:00 PM", "SCI241", 15);
  sec7 = c5.ScheduleSection('M', "4:10 - 6:00 PM", "ARTS25", 40);

  // Add these to the Schedule of Classes.

  scheduleOfClasses.AddSection(sec1);
  scheduleOfClasses.AddSection(sec2);
  scheduleOfClasses.AddSection(sec3);
  scheduleOfClasses.AddSection(sec4);
  scheduleOfClasses.AddSection(sec5);
  scheduleOfClasses.AddSection(sec6);
  scheduleOfClasses.AddSection(sec7);

Next, we use the AgreeToTeach method declared for the Professor class to assign Professor objects to Section objects:

  // Recruit a professor to teach each of the sections.

  p3.AgreeToTeach(sec1);
  p2.AgreeToTeach(sec2);
  p1.AgreeToTeach(sec3);
  p3.AgreeToTeach(sec4);
  p1.AgreeToTeach(sec5);
  p2.AgreeToTeach(sec6);
  p3.AgreeToTeach(sec7);

We then simulate student registration by having Students enroll in the various Sections using the Enroll method. Recall that this method returns one of a set of predefined status values—either SUCCESSFULLY_ENROLLED, SECTION_FULL, PREREQ_NOT_SATISFIED, or PREVIOUSLY_ENROLLED—and so in order to display which status is returned in each case, we created a ReportStatus method solely for the purpose of formatting an informational message (the ReportStatus method is discussed separately a bit later):

  Console.WriteLine("Student registration has begun!");
  Console.WriteLine("");

  // Students drop/add courses.

  Console.WriteLine("Student " + s1.Name +
      " is attempting to enroll in " + sec1.ToString());

  int status = sec1.Enroll(s1);

  // Note the use of a special method to interpret
  // and display the outcome of this enrollment request.
  // (We could have included the code inline here, but
  // since (a) it is rather complex and (b) it will need
  // to be repeated for all subsequent enrollment requests
  // below, it made sense to turn it into a reusable method
  // instead.)
  ReportStatus(status);

  Console.WriteLine("Student " + s1.Name +
      " is attempting to enroll in " + sec2.ToString());
  status = sec2.Enroll(s1);
  ReportStatus(status);

  Console.WriteLine("Student " + s2.Name +
      " is attempting to enroll in " + sec2.ToString());
  status = sec2.Enroll(s2);
  ReportStatus(status);

  Console.WriteLine("Student " + s2.Name +
      " is attempting to enroll in " + sec3.ToString());
  status = sec3.Enroll(s2);
  ReportStatus(status);

  Console.WriteLine("Student " + s2.Name +
      " is attempting to enroll in " + sec7.ToString());
  status = sec7.Enroll(s2);
  ReportStatus(status);

  Console.WriteLine("Student " + s3.Name +
      " is attempting to enroll in " + sec1.ToString());
  status = sec1.Enroll(s3);
  ReportStatus(status);

  Console.WriteLine("Student " + s3.Name +
      " is attempting to enroll in " + sec5.ToString());
  status = sec5.Enroll(s3);
  ReportStatus(status);

  // Output a blank line.
  Console.WriteLine("");

  // When the dust settles, here's what folks wound up
  // being registered for:
  //   Section sec1: Students s1, s3
  //   Section sec2: Student s2
  //   Section sec7: Student s2

Next, we simulate the assignment of grades at the end of the semester by invoking the postGrade method for each Student–Section combination:


  // Semester is finished (boy, that was quick!).
  // Professors assign grades.

  sec1.PostGrade(s1, "C+");
  sec1.PostGrade(s3, "A");
  sec2.PostGrade(s2, "B+");
  sec7.PostGrade(s2, "A-");

Finally, we put our various Display methods to good use by displaying the internal state of the various objects that we created—in essence, an "object dump":

  // Let's see if everything got set up properly
  // by calling various display methods!

  Console.WriteLine("====================");
  Console.WriteLine("Schedule of Classes:");
  Console.WriteLine("====================");
  Console.WriteLine("");
  scheduleOfClasses.Display();

  Console.WriteLine("======================");
  Console.WriteLine("Professor Information:");
  Console.WriteLine("======================");
  Console.WriteLine("");
  p1.Display();
  Console.WriteLine("");
  p2.Display();
  Console.WriteLine("");
  p3.Display();
  Console.WriteLine("");

  Console.WriteLine("====================");
  Console.WriteLine("Student Information:");
  Console.WriteLine("====================");
  Console.WriteLine("");
  s1.Display();
  Console.WriteLine("");
  s2.Display();
  Console.WriteLine("");
  s3.Display();
}

Here is the ReportStatus housekeeping method that we mentioned earlier; it simply translates the various constant int values into a printed string message:

public static void ReportStatus(int status) {
    if (status == Section.SUCCESSFULLY_ENROLLED) {
      Console.WriteLine("outcome: SUCCESSFULLY_ENROLLED");
    }
    else if (status == Section.PREREQ_NOT_SATISFIED) {
      Console.WriteLine("outcome: PREREQ_NOT_SATISFIED");
    }
    else if (status == Section.PREVIOUSLY_ENROLLED) {
      Console.WriteLine("outcome: PREVIOUSLY_ENROLLED");
    }
    else if (status == Section.SECTION_FULL) {
      Console.WriteLine("outcome: SECTION_FULL");
    }
}

Compiling the SRS

Now that we've written the initial version of the SRS, we can compile it. The simplest way to compile the source code is with the following command:

csc *.cs

However, if the code is compiled in this manner, the result will be an executable named Course.exe, because as we discussed in Chapter 13 an executable is by default named after the first source code file that is compiled. A file named Course.exe would indeed serve just fine as the SRS application's executable, but it would be more intuitive if the executable were named SRS.exe. To achieve this end, we can either rename the Course.exe file after the fact, or we can use the /out option when the source code is compiled, as follows:

csc /out:SRS.exe *.cs

As discussed in Chapter 13, the /out option is used to designate an output file name, in this case SRS.exe.

We then execute the SRS program simply by typing

SRS

at the command line.

When compiled and run, the SRS program produces the following command-line window output:


  Student registration has begun!

  Student Joe Blow is attempting to enroll in CMP101 - 1 - M - 8:10 - 10:00 PM
  outcome:  SUCCESSFULLY_ENROLLED
  Student Joe Blow is attempting to enroll in CMP101 - 2 - W - 6:10 - 8:00 PM
  outcome:  PREVIOUSLY_ENROLLED
  Student Gerson Lopez is attempting to enroll in CMP101 - 2 - W - 6:10 - 8:00 PM
  outcome:  SUCCESSFULLY_ENROLLED
  Student Gerson Lopez is attempting to enroll in OBJ101 - 1 - R - 4:10 - 6:00 PM
  outcome:  PREREQ_NOT_SATISFIED
  Student Gerson Lopez is attempting to enroll in ART101 - 1 - M - 4:10 - 6:00 PM
  outcome:  SUCCESSFULLY_ENROLLED
  Student Mary Smith is attempting to enroll in CMP101 - 1 - M - 8:10 - 10:00 PM
  outcome:  SUCCESSFULLY_ENROLLED
  Student Mary Smith is attempting to enroll in CMP283 - 1 - M - 6:10 - 8:00 PM
  outcome:  PREREQ_NOT_SATISFIED

  ====================
  Schedule of Classes:
  ====================

  Schedule of Classes for SP2004

  Section Information:
    Semester:  SP2004
    Course No.:  CMP101
    Section No:  2
    Offered:  W at 6:10 - 8:00 PM
    In Room:  GOVT202
    Professor:  John Carson
          Total of 1 student enrolled, as follows:
    Gerson Lopez

  Section Information:
    Semester:  SP2004
    Course No.:  CMP101
    Section No:  1
    Offered:  M at 8:10 - 10:00 PM
    In Room:  GOVT101
    Professor:  Jackie Chan
          Total of 2 students enrolled, as follows:
    Mary Smith
    Joe Blow
  Section Information:
    Semester:  SP2004
    Course No.:  CMP283
    Section No:  1
    Offered:  M at 6:10 - 8:00 PM
    In Room:  GOVT101
    Professor:  Jacquie Barker
  Total of 0 students enrolled.

  Section Information:
    Semester:  SP2004
    Course No.:  CMP999
    Section No:  1
    Offered:  R at 4:10 - 6:00 PM
    In Room:  SCI241
    Professor:  John Carson
  Total of 0 students enrolled.

  Section Information:
    Semester:  SP2004
    Course No.:  OBJ101
    Section No:  2
    Offered:  T at 6:10 - 8:00 PM
    In Room:  SCI330
    Professor:  Jackie Chan
  Total of 0 students enrolled.

  Section Information:
    Semester:  SP2004
    Course No.:  OBJ101
    Section No:  1
    Offered:  R at 4:10 - 6:00 PM
    In Room:  GOVT105
    Professor:  Jacquie Barker
  Total of 0 students enrolled.

  Section Information:
    Semester:  SP2004
    Course No.:  ART101
    Section No:  1
    Offered:  M at 4:10 - 6:00 PM
    In Room:  ARTS25
    Professor:  Jackie Chan
           Total of 1 student enrolled, as follows:
    Gerson Lopez
  ======================
  Professor Information:
  ======================

  Person Information:
    Name:  Jacquie Barker
    Soc. Security No.:  123-45-6789
  Professor-Specific Information:
    Title:  Adjunct Professor
    Teaches for Dept.:  Information Technology
  Teaching Assignments for Jacquie Barker:
    Course No.:  OBJ101
    Section No.:  1
    Course Name:  Object Methods for Software Development
    Day and Time:  R - 4:10 - 6:00 PM
    -----
    Course No.:  CMP283
    Section No.:  1
    Course Name:  Higher Level Languages (C#)
    Day and Time:  M - 6:10 - 8:00 PM
    -----

  Person Information:
    Name:  John Carson
    Soc. Security No.:  567-81-2345
  Professor-Specific Information:
    Title:  Full Professor
    Teaches for Dept.:  Information Technology
  Teaching Assignments for John Carson:
    Course No.:  CMP101
    Section No.:  2
    Course Name:  Beginning Computer Technology
    Day and Time:  W - 6:10 - 8:00 PM
    -----
    Course No.:  CMP999
    Section No.:  1
    Course Name:  Living Brain Computers
    Day and Time:  R - 4:10 - 6:00 PM
    -----

  Person Information:
    Name:  Jackie Chan
    Soc. Security No.:  987-65-4321
  Professor-Specific Information:
    Title:  Full Professor
    Teaches for Dept.:  Information Technology
  Teaching Assignments for Jackie Chan:
    Course No.:  CMP101
    Section No.:  1
    Course Name:  Beginning Computer Technology
    Day and Time:  M - 8:10 - 10:00 PM
    -----
    Course No.:  OBJ101
    Section No.:  2
    Course Name:  Object Methods for Software Development
    Day and Time:  T - 6:10 - 8:00 PM
    -----
    Course No.:  ART101
    Section No.:  1
    Course Name:  Beginning Basketweaving
    Day and Time:  M - 4:10 - 6:00 PM
    -----

  ====================
  Student Information:
  ====================

  Person Information:
    Name:  Joe Blow
    Soc. Security No.:  111-11-1111
  Student-Specific Information:
    Major:  Math
    Degree:  M.S.
  Course Schedule for Joe Blow
  Transcript for:  Joe Blow (111-11-1111) [M.S. - Math]
    Semester:         SP2004
    Course No.:       CMP101
    Credits:          3.0
    Grade Received:  C+
    -----
  Person Information:
    Name:  Gerson Lopez
    Soc. Security No.:  222-22-2222
  Student-Specific Information:
    Major:  Information Technology
    Degree:  Ph. D.
  Course Schedule for Gerson Lopez
  Transcript for:  Gerson Lopez (222-22-2222) [Ph. D. - Information Technology]
    Semester:         SP2004
    Course No.:       CMP101
    Credits:          3.0
    Grade Received:  B+
    -----
    Semester:          SP2004
    Course No.:        ART101
    Credits:           3.0
    Grade Received:  A-
    -----

  Person Information:
    Name:  Mary Smith
    Soc. Security No.:  333-33-3333
  Student-Specific Information:
    Major:  Physics
    Degree:  B.S.
  Course Schedule for Mary Smith
  Transcript for:  Mary Smith (333-33-3333) [B.S. - Physics]
    Semester:         SP2004
    Course No.:       CMP101
    Credits:          3.0
    Grade Received:  A
    -----

The preceding output demonstrates that the SRS is working properly! Of course, the SRS driver program could be extended to test various other scenarios; some of the exercises at the end of this chapter suggest ways that you might wish to try doing so.


Team LiB
Previous Section Next Section