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