Previous Section  < Free Open Study >  Next Section

Selected C++ Example #2


// Example #2 



// This C++ example illustrates the checking of a student's

// course prerequisites without using controller classes.

// This puts policy information inside one of the classes

// involved in the policy, rendering it less reusable.

// I feel that controller classes add complexity to design

// with little or no benefits. While controller classes do allow

// their host classes to be more reusable outside of the current

// domain, they are reusable only because they don't do anything,

// that is, they have data and a bunch of get and set methods.

// In this example, in order to check prerequisites, the

// course offering will ask the student for his/her list of

// courses that he/she has taken. The course offering will

// ask the Course to verify that the student has the necessary

// prerequisites by passing the course list as an explicit

// argument. The Course object then makes the determination.



// This example has several examples of containment in its

// implementation. For those readers who do not yet understand

// containment, I recommend reading Chapter 4 before working

// through this example. Experienced readers will notice areas

// where inheritance would have been very useful. Most noticeably

// in the area of reference counting. I felt that the addition

// of inheritance would have caused too many forward references

// into the text. It was left out at the expense of some

// redundant abstraction.



// The efficiency-minded should be aware that this code

// was written for understanding, not speed. All methods are not

// inline. The one-liners could easily be made inline. Also,

// some of the conditional tests could be improved for speed

// at the cost of readability.



#include <iostream.h>

#include <string.h>

#include <stdlib.h>

#include <stdarg.h>



// Forward references to the list classes.

class CourseList;

class StudentList;

class OfferingList;





// Constants used within this program.



const int name_len = 30;

const int desc_len = 128;

const int course_len = 30;

const int student_len = 50;

const int small_strlen = 15;



// Courses have a name, description, duration, and a list

// of prerequisites. Since we expect Course objects to

// appear on many lists (e.g., Calculus I is a prerequisite of

// Calculus II, Calculus III, Physics I, etc.), we would like

// to implement shared shallow copies, i.e., we copy only

// pointers, not the entire course object. We accomplish

// this through a technique called reference counting. The

// class gets an integer counter, which maintains how many

// objects are sharing the particular course object. Anyone

// who points at the object must call the Course::attach_object()

// method, which increments the counter. When destroyed, the

// sharing object calls Course::detach_object() to decrement

// the counter. If detach_object() returns zero, then the

// caller knows that it is the last user of that Course

// object and it calls its destructor.



class Course {

private:

     char name[name_len];

     char description[desc_len];

     int duration;

     CourseList* prereq;

     int reference_count;

public:

     Course(char*, char*, int, int, ...);

     Course(const Course&);

     ~Course();

     int attach_object();

     int detach_object();

     void add_prereq(Course&);

     int check_prereq(CourseList&);

     void print();

     void short_print();

     int are_you(char*);

};





// Each key abstraction also has a corresponding list class

// to maintain the list operations. Readers with more

// experience would certainly argue for the use of C++ templates

// to handle the list classes. I felt that this was too much

// forward referencing, which would have rendered the example less

// readable.



class CourseList {

private:

     Course **courses;

     int size;

     int course_num;

public:

     CourseList(int);

     CourseList(CourseList&);

     ~CourseList();

     int add_item(Course&);

     Course* find_item(char*);

     int find_all(CourseList&);

     void print();

};





// This constructor for course takes a name, description,

// duration, and a variable list of prerequisites. Each

// prerequisite is added to the list using the CourseList::

// add_item() method.

Course::Course(char* n, char* d, int len, int pnum, ...)

{

     int i;

     va_list ap;



     strncpy(name, n, name_len);

     strncpy(description, d, desc_len);

     duration = len;

     prereq = new CourseList(course_len);

     reference_count = 1;

     if (pnum) {

          va_start(ap, pnum);

          for (i=0; i < pnum; i++) {

                      prereq->add_prereq(*va_arg(ap, Course*));

          }

          va_end(ap);

     }

}



// The copy constructor for course makes a copy of all the

// strings and calls the copy constructor for a CourseList.

Course::Course(const Course& rhs)

{

     strcpy(name, rhs.name);

     strcpy(description, rhs.description);

     duration = rhs.duration;

     prereq = new CourseList(*rhs.prereq);

     reference_count = rhs.reference_count;

}





// The destructor for Course deletes its prerequisites and

// checks to be sure that it is the last user that called

// delete on the course object. If not, an error message

// is displayed.

Course::~Course()

{

     delete prereq;

     if (reference_count > 1) {

          cout << ''Error: A course object destroyed with '';

          cout << reference_count << '' other objects referencing

                        it.\n'';

     }

}





// Each object that points at a Course object must call

// attach_object to register itself with the object.

int

Course::attach_object()

{

     return(++reference_count);

}



// Each object that called attach_object must call detach_object

// in its destructor (or other member function) to decrement

// the reference counter.

int

Course::detach_object()

{

     return(--reference_count);

}





// To add a prerequisite to the course, we call the add_item

// method of CourseList. This method returns zero on failure

// to add the Course (i.e., the list is full).

void

Course::add_prereq(Course&new_prereq)

{

     if (prereq->add_item(new_prereq) == 0) {

          cout << ''Error: Cannot add any new prerequisites.\n'';

     }

}





void

Course::print()

{

     cout << ''\n\nCourse:'' << name << ''\n'';

     cout << ''Description:'' << description << ''\n'';

     cout << ''Duration:'' << duration << ''\n'';

     cout << ''List of Prerequisites: '';

     prereq->print();

     cout << ''\n\n'';

}





// The short_print method is used in places where we only

// want to see the name and not all of the associated info

// of the Course.

void

Course::short_print()

{

     cout << name;

}







// This method is very important in the design of this

// system. The Course object receives a course list and

// checks its prerequisites against it, using the CourseList::

// find_all method. This method checks to see if all of the

// courses in the argument list are in the list to which the

// message was sent.

int

Course::check_prereq(CourseList& courses_taken)

{

     return(courses_taken.find_all(*prereq));

}





// This method checks to see if its name is equal to the

// name passed in. It is used for searching for a particular

// course from a list of Course objects (by name).

int

Course::are_you(char* guess_name)

{

     return(!strcmp(name, guess_name));

}





CourseList::CourseList(int sz)

{

     course_num = 0;

     courses = new Course*[size=sz];

}





// Note the use of reference counting in the CourseList copy

// constructor. Each course is simply pointed to, not copied.

// The attach_object method allows the incrementing of the

// reference counter. This reference counter keeps track of

// how many objects (CourseList or otherwise) are pointing to

// the Course object.

CourseList::CourseList(CourseList& rhs)

{

     int i;



     courses = new Course*[size=rhs.size];

     for (i=0; i < size; i++) {

          courses[i] = rhs.courses[i];

          courses[i]->attach_object();

     }

     course_num = rhs.course_num;

}





// The CourseList destructor detaches each object in the

// prerequisite list. If any of the detach_object method

// calls evaluates to zero, this course list is the

// last object using the course and its destructor must be

// called.

CourseList::~CourseList()

{

     int i;

     for (i=0; i < course_num; i++) {

          if (courses[i]->detach_object() == 1) {

                     delete courses[i];

          }

     }

     delete courses;

}





// The add_item method checks to be sure the list still has

// room. A reasonable solution would be to increase the

// size of the list on demand. This adds a significant amount

// of code and complexity without enlightening the reader

// to the problem at hand. For that reason, lists are a fixed

// size at creation time. They report an error when full (by

// returning zero).

// If the list has room, then the course is added and it is

// attached to preserve the reference counting.

int

CourseList::add_item(Course&new_item)

{

     if (course_num == size) {

          return(0);

     }

     else {

          courses[course_num++] = &new_item;

          new_item.attach_object();

     }

     return(1);

}





// The course list searches its list for the course whose

// name matches that passed in by the user. If the course

// isn't found, this method returns the NULL pointer.

Course*

CourseList::find_item(char* guess_name)

{

     int i;



     for (i=0; i < course_num; i++) {

          if (courses[i]->are_you(guess_name)) {

                     return(courses[i]);

          }

     }

     return(NULL);

}





// This method checks to be sure that all courses of the

// findlist are in the list to which the message was sent.

// Since courses are shallow copied in these lists, we need

// only check the addresses of the course objects and not

// the course names.

int

CourseList::find_all(CourseList& findlist)

{

     int i, j, found;



     for (i=0; i < findlist.course_num; i++) {

          found = 0;

          for (j=0; j < course_num && ! found; j++) {

                      if (findlist.courses[i] == courses[j]) {

                                 found=1;

                      }

          }

          if (!found) {

                    return(0);

          }

     }

     return(1);

}





void

CourseList::print()

{

     int i;



     cout << ''\n\n'';

     for (i=0; i < course_num; i++) {

          courses[i]->short_print();

          cout << '' '';

     }

     cout << ''\n\n'';

}



// Students have a name, social security number, and age. Like

// the Course objects, they also have a list of courses (the

// courses that the student has completed). Students are

// a reference-counting class just like the Course class. The

// reference counting works in exactly the same way as that of

// the Course. This will lead the experts to criticize the

// duplicate abstraction caused by the reference-counting

// mechanism. We could use inheritance to solve the problem

// but have not discussed this topic yet. The use of inheritance

// to eliminate duplicate abstractions will be examined in

// Chapter 5.

class Student {

private:

     char name[name_len];

     char ssn[small_strlen];

     int age;

     CourseList *courses;

     int reference_count;

public:

     Student(char*, char*, int, int, ...);

     Student(const Student&);

     ~Student();

     int attach_object();

     int detach_object();

     void add_course(Course&);

     CourseList& get_courses();

     void print();

     void short_print();

     int are_you(char*);

};





// The StudentList mirrors the CourseList class except it is

// working on Student objects rather than Course objects.

class StudentList{

private:

     Student **students;

     int size;

     int student_num;

public:

     StudentList(int);

     StudentList(StudentList&);

     ~StudentList();

     int add_item(Students);

     Student* find_item(char*);

     void print();

};





Student::Student(char* n, char* s, int a, int num, ...)

{

     int i;

     va_list ap;



     strncpy(name, n, name_len);

     strncpy(ssn, s, small_strlen);

     age = a;

     courses = new CourseList(course_len);

     reference_count = 1;

     if (num) {

          va_start(ap, num);

          for (i=0;i < num; i++) {

                       courses->add_item(*va_arg(ap, Course*));

          }

          va_end(ap);

     }

}





Student::Student(const Student& rhs)

{

     strcpy(name, rhs.name);

     strcpy(ssn, rhs.ssn);

     age = rhs.age;

     courses = new CourseList(*rhs.courses);

     reference_count = rhs.reference_count;

}



Student::-Student()

{

     delete courses;

}





int

Student::attach_object()

{

     return(++reference_count);

}





int

Student::detach_object()

{

     return(--reference_count);

}





void

Student::add_course(Course& c)

{

     if (courses->add_item(c) == 0){

          cout << ''Cannot add any new courses to the Student.\n'';

     }

}



// Note the need for an accessor method. This method will

// be used by the CourseOffering class when it needs to

// check the prerequisites of a Student object. The Student

// is asked for its course list, which is then given to the

// course for processing. In general, accessor methods are

// bad in that they imply that this piece of data is not

// strongly related to the other data of this class or its

// methods. In general, ask why you are removing this data

// from its encapsulation, what you are doing with it, and

// why doesn't the class that owns the data do it for you.

// In this example, the class cannot perform the behavior itself

// because it needs data from both the Course and Student objects

// to make the decision.

CourseList&

Student::get_courses()

{

     return(*courses);

}





void

Student::print()

{

     cout << ''\n\nName: '' << name << ''\n'';

     cout << ''SSN: '' << ssn << ''\n'';

     cout << ''Age: '' << age << ''\n'';

     cout << ''Prerequisites: '';

     courses->print();

     cout << ''\n\n'';

}





void

Student::short_print()

{

     cout << name;

}





int

Student::are_you(char* guess_name)

{

     return(!strcmp(name, guess_name));

}





StudentList::StudentList(int sz)

{

     student_num = 0;

     students = new Student*[size=sz];

}





StudentList::StudentList(StudentList& rhs)

{

     int i;



     students = new Student*[size=rhs.size];

     for (i=0; i < size; i++) {

          students[i] = rhs.students[i];

          students[i]->attach_object();

     }

     student_num = rhs.student_num;

}





StudentList::~StudentList()

{

     int i;

     for (i=0; i < student_num; i++) {

          if (students[i]->detach_object() == 1) {

                     delete students[i];

          }

     }

     delete students;

}





int

StudentList::add_item(Student& new_item)

{

     if (student_num == size) {

          return(0);

     }

     else {

          students[student_num++] = &new_item;

          new_item.attach_object();

     }

     return(1);

}





Student*

StudentList::find_item(char* guess_name)

{

     int i;



     for (i=0; i < student_num; i++) {

          if (students[i]->are_you(guess_name)) {

                     return(students[i]);

          }

     }

     return(NULL);

}





void

StudentList::print()

{

     int i;



     for (i=0; i < student_num; i++) {

          students[i]->short_print();

          cout << '' '';

     }

}





// The CourseOffering class captures the relationship of a

// course, in a room, on a particular date, with a particular

// group of students. It is not a reference-counting class,

// because we never share CourseOffering objects on multiple

// lists.

class CourseOffering {

private:

     Course* course;

     char room[small_strlen];

     char date[small_strlen];

     StudentList *attendees;

public:

     CourseOffering(Course&, char*, char*);

     CourseOffering(const CourseOffering&);

     -CourseOffering();

     void add_student(Student&);

     void print();

     void short_print();

     int are_you(char*, char*);

};





// The CourseOffering list class is similar to the Student

// and Course list classes.

class OfferingList {

private:

     CourseOffering **offerings;

     int size;

     int offering_num;

public:

     OfferingList(int);

     OfferingList(OfferingList&);

     ~OfferingList();

     int add_item(CourseOffering&);

     CourseOffering* find_item(char*, char*);

     void print();

};





CourseOffering::CourseOffering(Course& c, char* r, char* d)

{

     course = &c;

     course->attach_object();

     strncpy(room, r, small_strlen);

     strncpy(date, d, small_strlen);

     attendees = new StudentList(student_len);

}





CourseOffering::CourseOffering(const CourseOffering& rhs)

{

     course = rhs.course;

     course->attach_object();

     strcpy(room, rhs.room);

     strcpy(date, rhs.date);

     attendees = new StudentList(*rhs.attendees);

}





CourseOffering::~CourseOffering()

{

     if (course->detach_object() == 1) {

          delete course;

     }

     delete attendees;

}





// The course offering must ensure that a new student has

// the necessary prerequisites. It does this by getting

// the list of courses the student has taken from the Student

// and gives it to the check_prereq method of the course.

// The course can determine if the prerequisites are met,

// since the course has the list of prerequisites and the

// Student has given, via the CourseOffering object's call

// to get_courses, the list of courses.

void

CourseOffering::add_student(Student& new_student)

{

     if (course->check_prereq(new_student.get_courses())) {

          attendees->add_item(new_student);

          cout << ''Student added to course.\n'';

     }

     else {

          cout << ''Admission refused: Student does not have the '';

          cout << ''necessary prerequisites.\n'';

     }

}





void

CourseOffering::print()

{

     cout << ''\n\nThe course offering for '';

     course->short_print();

     cout << '' will be held in room '' << room << '' starting on '';

     cout << date << ''\n'';

     cout << ''Current attendees include: '';

     attendees->print();

     cout << ''\n\n'';

}





void

CourseOffering::short_print()

{

     course->short_print();

     cout << ''('' << date << '') '';

}





// The name of the course is not enough when comparing course

// offerings. We must also test the dates.

int

CourseOffering::are_you(char* guess_name, char* guess_date)

{

     return(!strcmp(guess_date, date) &&

                course->are_you(guess_name));

}





OfferingList::OfferingList(int sz)

{

     offering_num = 0;

     offerings = new CourseOffering*[size=sz];

}





OfferingList::OfferingList(OfferingList& rhs)

{

     int i;



     offerings = new CourseOffering*[size=rhs.size];

     for (i=0; i < size; i++) {

     offerings[i] = rhs.offerings[i];

     }

     offering_num = rhs.offering_num;

}





OfferingList::~OfferingList()

{

     int i;

     for (i=0; i < offering_num; i++) {

          delete offerings[i];

     }

     delete offerings;

}





int

OfferingList::add_item(CourseOffering& new_item)

{

     if (offering_num == size) {

          return(0);

     }

     else {

          offerings[offering_num++] = &new_item;

     }

     return(1);

}





CourseOffering*

OfferingList::find_item(char* guess_name, char* date)

{

     int i;



     for (i=0; i < offering_num; i++) {

          if (offerings[i]->are_you(guess_name, date)) {

                     return(offerings[i]);

          }

     }

     return(NULL);

}





void

OfferingList::print()

{

     int i;



     for (i=0; i < offering_num; i++) {

          offerings[i]->short_print();

          cout << '' '';

     }

}





// The main program is a simple menu-driven system for creating

// courses, course offerings, students; listing courses,

// students, and offerings; adding courses to students,

// students to courses, and prerequisites to courses. It can

// be used to test the public interfaces of the classes in

// this application.

void

main()

{

     CourseList courses(50);

     StudentList students(50);

     OfferingList offerings(50);

     Course *course1, *course2;

     Student *student;

     CourseOffering *offer1;

     int duration, age, choice;

     char answer[128], name[40], description[128],

                course_name[50];

     char ssn[20], date[20], room[20];

     char c;



     do {

          cout << ''What would you like to do?\n'';

          cout << '' 1) Build a new course\n'';

          cout << '' 2) Build a new student\n'';

          cout << '' 3) Build a new course offering\n'';

          cout << '' 4) List courses\n'';

          cout << '' 5) List students\n'';

          cout << '' 6) List offerings\n'';

          cout << '' 7) Add a prerequisite to a course\n'';

          cout << '' 8) Add a course to a student\n'';

          cout << '' 9) Add a student to a course offering\n'';

          cout << ''10) Detailed info on a course\n'';

          cout << ''11) Detailed info on a student\n'';

          cout << ''12) Detailed info on an of fering\n'';

          cout << '' q) Quit\n'';

          cout << ''\nYour Choice: '';

          cin.getline(answer, 128);

          choice = atoi(answer);

          switch (choice) {

                     case 1:

                               cout << ''Enter Name: '';

                               cin.getline(name, 40);

                               cout << ''Enter Description: '';

                               cin.getline(description, 128);

                               cout << ''Enter Length of Course: '';

                               cin >> duration;

                               courses.add_item(*new Course(name,

                                description, duration, 0));

                               cin.get(c);

                               break;

                     case 2:

                               cout << ''Enter name: '';

                               cin.getline(name, 40);

                               cout << ''Enter ssn: '';

                               cin.getline(ssn, 20);

                               cout << ''Enter age: '';

                               cin >> age;

                               students.add_item(*new Student

                                (name, ssn, age, 0));

                               cin.get(c);

                               break;

                     case 3:

                               cout << ''Enter course: '';

                               cin.getline(course_name, 50);

                               course1 = courses.find_item(course_name);

                               if (course1 == NULL) {

                                         cout << ''Sorry, Cannot

                               find that course.\n'';

                                         break;

                               }

                               cout << ''Enter room: '';

                               cin.getline(room, 20);

                               cout << ''Enter date: '';

                               cin.getline(date, 20);

                               offerings.add_item(*new

                                CourseOffering(*course1,

                                room, date));

                               break;

                     case 4:

                               cout << ''\nList of courses: \n'';

                               courses.print();

                               cout << ''\n\n'';

                               break;

                     case 5:

                               cout << ''\nList of students: \n'';

                               students.print();

                               cout << ''\n\n'';

                               break;

                     case 6:

                               cout << ''\nList of Offerings: \n'';

                               offerings.print();

                               cout << ''\n\n'';

                               break;

                     case 7:

                               cout << ''To which course? '';

                               cin.getline(course_name, 50);

                               course1 = courses.find_item

                                (course_name);

                               if (course1 == NULL) {

                                         cout << ''Sorry, Cannot

                               find that course.\n'';

                                         break;

                               }

                               cout << ''Which prerequisite? '';

                               cin.getline(course_name, 50);

                               course2 = courses.find_item

                                (course_name);

                               if (course2 == NULL) {

                                         cout << ''Sorry, Cannot

                                find that course.\n'';

                                          break;

                               }

                               course1->add_prereq(*course2);

                               break;

                     case 8:

                               cout << ''To Which Student? '';

                               cin.getline(name, 40);

                               student = students.find_item(name);

                               if (student == NULL) {

                                         cout << ''Sorry, Cannot

                               find that student.\n'';

                                         break;

                               }

                               cout << ''Which Course? '';

                               cin.getline(course_name, 50);

                               course1 = courses.find_item

                                (course_name);

                               if (course1 == NULL) {

                                         cout << ''Sorry, Cannot

                                find that course.\n'';

                                          break;

                               }

                               student->add_course(*course1);

                               break;

                     case 9:

                               cout << ''To which course? '';

                               cin.getline(course_name, 50);

                               cout << ''On which date? '';

                               cin.getline(date, 20);

                               offer1 = offerings.find_item

                                (course_name, date);

                               if (offer1 == NULL) {

                                         cout<<''Sorry, Cannot

                                find that course offering.\n'';

                                          break;

                               }

                               cout << ''Which Student? '';

                               cin.getline(name, 40);

                               student = students. find_item(name);

                               if (student == NULL) {

                                     cout << ''Sorry, Cannot

                               find that student.\n'';

                                          break;

                               }

                               offer1->add_student(*student);

                               break;

                     case 10:

                               cout << ''On Which Course? '';

                               cin.getline(course_name, 50);

                               course1 = courses.find_item

                                (course_name);

                               if (course1 == NULL) {

                                         cout << ''Sorry, Cannot

                                find that course.\n'';

                                           break;

                               }

                               course1->print();

                               break;

                     case 11:

                               cout << ''On Which Student?'';

                               cin.getline(name, 40);

                               student = students.find_item(name);

                               if (student == NULL) {

                                         cout << ''Sorry, Cannot

                                find that student.\n'';

                                           break;

                               }

                               student->print();

                               break;

                     case 12:

                               cout << ''On Which Course? '';

                               cin.getline(course_name, 50);

                               cout << ''Which date? '';

                               cin.getline(date, 20);

                               offer1 = offerings.find_item

                                (course_name, date);

                               if (offer1 == NULL) {

                                         cout << ''Sorry, Cannot

                                find that course offering.\n'';

                                          break;

                               }

                               offer1->print();

                               break;

                               }

                               } while (answer[0] >= '1' &&

                                 answer[0] <= '9');

                               }

    Previous Section  < Free Open Study >  Next Section