Section 7 - Analysis of an OOP Project

7A.7.1 General Strategy: Class vs. Test Main

When we are given a project description in this course, there are two aspects to consider.  The first is the class specification.  The class is intended for many different users, not just you.  So you must define it independently of the actual client application (i.e., independently of main()) and only include data and methods useful to the wide range of potential applications.  The second is the client, which usually means the main().  The client is really only a test of the class, but I do give you requirements for its behavior, as well, so you must design the main() carefully and according to spec.  Think of these as two distinct parts of the task.

Our approach will be to first build the class and test it in a small client (main()).  In fact, we won't even try to build the entire class at once.  We will do it in stages, completing and testing each stage before we move on to the next.

7A.7.2 The Example Project Specification

You are to define a class called Patient

Patient will have the following instance members:

The id is meant to represent a patient ID that might be used to track a patient in an E.R. or hospital.  The temperature is the Fahrenheit temperature that the patient presents upon admission.

The class should only allow string names that have between 2 and 40 characters.  It will only allow ids that are between 0 and 9999.  It will only allow temperatures between 88 and 111 degrees.  Any other value will be considered a user error and we will not allow the client to set such values.  This is true of all applications that use the class, so these limits are built-into the class, not the client.  These limits, which are part of the class, should be public static members that are constants.  Also, there should be an alarm temperature of 103.5 (static) that will be used to set off an alarm.  I will describe the alarm in a moment.

You should provide the following instance methods:

Provide a test main that instantiates two Patient objects and gets the data for each of them from the user, interactively.  It then compares the temperatures of the two Patient objects and displays the names of the patients, making sure that the name of the patient with the higher temperature is listed first (under the assumption that a patient with a higher temperature requires more immediate attention than a patient with a lower one).  Test this in multiple runs and show that the patient with the higher temperature is always displayed first.

7A.7.3 Constructor and display() - The Framework Phase

The first step is to get the framework of the class and a small main() designed and debugged.  We will do the minimum necessary to create a working program, then build from there.  We will call this the Framework Phase.  Often, if you are stuck deep in the implementation of an assignment, I will ask to see your Framework Phase so save a copy of it in a separate file before you move on to the next phases.

In the Framework Phase we create only:

We are not doing any mutators.  We are not doing anything with static members. One step at a time, thank you very much.

You should be able to do this small part in about 15 minutes.  Try it, and after you get it working, look at my solution:

// ---------------- SOURCE -------------------------
#include <iostream>
#include <string>
using namespace std;

// class prototype
class Patient
{
private:
string name;
int id;
double temperature;

public:
// Default constructor
Patient();

void display();
};

int main()
{
// instantiate two Patients
Patient person1, person2;

// display both
person1.display();
person2.display();

return 0;
}

// class method definitions
Patient::Patient()
{
name = "nobody";
temperature = 98.6;
id = 0;
}

void Patient::display()
{
cout << "Patient: "
<< "\n  Name: " << name
<< "\n  ID: " << id
<< "\n  Body Temperature: " << temperature << " (F)" << endl;
}

/* ------------- RUN (OUTPUT) ----------------------
Patient:
Name: nobody
ID: 0
Body Temperature: 98.6 (F)
Patient:
Name: nobody
ID: 0
Body Temperature: 98.6 (F)
Press any key to continue . . .
--------------- END OF RUN ------------------------- */

This was easy, but you have to actually do it to reap the benefit.  Also, you can't just write it.  You have to fully debug and run it.

Now we have a framework on which we can bang, cut, drill and craft.

7A.7.4 Adding the Statics and Mutators - The Class Phase(s)

We are going to add the rest of the class members but not do the full main() yet.  We will still keep main() very simple, to test out the class and debug it if necessary.

We add the static members:

We also add:

We will write a test main that does the following:

This is the Class Phase, and sometimes it is broken into sub-phases.  For instance, if there are a lot of class methods, you might do a few mutators, get those working, add a few more, get that working, then add some more elaborate instance methods that might not be mutators or accessors.   As with the Framework Phase, I expect that you will have done this if you are asking for  help to debug the way the assigned client interacts with the assigned class.  You would not be attempting to write the assigned client if you had not first done the Class Phase, with a simple client and debugged that.  (Of course, you can always ask for help if you are working on the Class Phase and get stuck.)

In our current effort, if we have done everything correctly, we'll see the alarm on Janis, but not on Fred. Again, please try it yourself.  It is better for you to get stuck and ask questions on this in the public forums rather than to wait and be overwhelmed in an assignment.

// ---------------- SOURCE -------------------------
#include <iostream>
#include <string>
using namespace std;

// class prototype
class Patient
{
public:
static const int MIN_LENGTH = 2;
static const int MAX_LENGTH = 40;
static const int MIN_ID = 0;
static const int MAX_ID = 9999;
static const double MIN_TEMP;  // we use alternate initialization, below
static const double MAX_TEMP;
static const double ALARM_TEMP;
static const string DEFAULT_NAME;
static const int DEFAULT_ID = 0;
static const int DEFAULT_TEMP = 98.6;

private:
string name;
int id;
double temperature;

public:
Patient();
Patient( string name, double temperature, int id );
void display();

// Accessors can be done in line:
double getTemperature() { return temperature; }
int getID() { return id; }
string getName() { return name; }

// mutators
bool setTemperature(double temperature);
bool setID(int id);
bool setName(string name);
};

// alternate way to initialize a static (required for not const ints)
const double Patient::ALARM_TEMP = 103.5;
const double Patient::MIN_TEMP = 88.;
const double Patient::MAX_TEMP = 111.;
const string Patient::DEFAULT_NAME = "nobody";

int main()
{
// instantiate three Patients
Patient person1, person2;
Patient person3("Racha", 98.7, 32);

// make changes to two
person1.setName("Fred");
person1.setID(1234);
person1.setTemperature(100.5);

person2.setName("Janis");
person2.setID(5555);
person2.setTemperature(103.7);

// display all
person1.display();
person2.display();
person3.display();

// test a couple mutators for data filtering
if ( !person1.setTemperature(333) )
cout << "Unable to set temperature to 333." << endl;
else
cout << "Temp set to 333." << endl;

if ( !person2.setID(-44) )
cout << "Unable to set ID to -44." << endl;
else
cout << "ID set to -44." << endl;
cout << endl;

// test a few accessors
cout << "Patient #1's name is " << person1.getName() << endl;
cout << "The minimum valid temperature is " << Patient::MIN_TEMP << endl;

return 0;
}

// class method definitions
Patient::Patient()
{
name = DEFAULT_NAME;
temperature = DEFAULT_TEMP;
id = DEFAULT_ID;
}

Patient::Patient( string name, double temperature, int id )
{
if ( !setName( name ) )
this->name = DEFAULT_NAME;
if ( !setTemperature( temperature ) )
this->temperature = DEFAULT_TEMP;
if ( !setID( id ) )
this->id = DEFAULT_ID;
}

void Patient::display()
{
cout << "Patient: "
<< "\n  Name: " << name
<< "\n  ID: " << id
<< "\n  Body Temperature: " << temperature << " (F)";
if (temperature > ALARM_TEMP)
cout << "\n   *** urgent: attend immediately ***";
cout << endl;
}

// mutators
bool Patient::setTemperature(double temperature)
{
if (temperature < MIN_TEMP || temperature > MAX_TEMP)
return false;
this->temperature = temperature;
return true;
}

bool Patient::setID(int id)
{
if (id < MIN_ID || id > MAX_ID)
return false;
this->id = id;
return true;
}

bool Patient::setName( string name)
{
if (name.length() < MIN_LENGTH || name.length() > MAX_LENGTH)
return false;
this->name = name;
return true;
}

/* ------------- RUN (OUTPUT) ----------------------
Patient:
Name: Fred
ID: 1234
Body Temperature: 100.5 (F)
Patient:
Name: Janis
ID: 5555
Body Temperature: 103.7 (F)
*** urgent: attend immediately ***
Patient:
Name: Racha
ID: 32
Body Temperature: 98.7 (F)
Unable to set temperature to 333.
Unable to set ID to -44.

Patient #1's name is Fred
The minimum valid temperature is 88
Press any key to continue . . .------------------- */

Notice that I defined the parameter-taking constructor so that it calls the mutators.  Once you have mutators, you should use them from that constructor so that all changes to members go through the mutator methods and nowhere else.  This is more important in constructors that take parameters, and it is optional in default constructors since you may assign values directly there.

This probably took about an hour or two to write and debug.  You could have broken this into steps and done only one mutator and accessor first, say, the mutator and accessor associated with the name member, debugged it, then went on to do the others.

Note that this main() did not completely test everything. Ideally, you would do a thorough check of your class methods before proceeding to the next phase.  Keep a copy of the final Class Phase and client so you can show them to me if you need help with something further down stream.  I'll ask to see them to help you isolate your problem.

At this point our class is about done and all we need to do is design and write our sample main().  Next page.