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'); } |