OOP Collaboration


Sharing of processes is a hallmark of how real-world objects collaborate. A student cannot attend class unless the student enters into collaboration with the registrar to enroll in a course. Therefore, the student is said to share the process performed by the registrar needed to enroll students into courses.

In object-oriented programming, collaboration occurs among objects that mimic real-world objects inside an application. Throughout this book, you ve learned that an object is a person, place, or thing that has attributes and behaviors. As you ll recall, an attribute is data, and a behavior is a process referred to as a function or method, depending on the object-oriented programming language being used to develop the application.

Let s see how collaboration works by continuing the course registration example we ve used throughout this book. Here is the Student class we discussed extensively in other chapters:

 class Student 
{
protected:
int m_Graduation, m_ID;
char m_First[16], m_Last[16];
public:
virtual void Display()
{
cout << "ID: " << m_ID << endl;
cout << "First: " << m_First << endl;
cout << "Last: " << m_Last << endl;
cout << "Graduation: " << m_Graduation << endl;
}
void Modify( int ID, char First[], char Last[],
int Graduation )
{
m_ID = ID;
strcpy( m_First, First );
strcpy( m_Last, Last );
m_Graduation = Graduation;
}
Student()
{
m_ID = m_Graduation = 0;
m_First[0] = m_Last[0] = '
 class Student 
{
protected:
int m_Graduation, m_ID;
char m_First[16], m_Last[16];
public:
virtual void Display()
{
cout << "ID: " << m_ID << endl;
cout << "First: " << m_First << endl;
cout << "Last: " << m_Last << endl;
cout << "Graduation: " << m_Graduation << endl;
}
void Modify( int ID, char First[], char Last[],
int Graduation )
{
m_ID = ID;
strcpy ( m_First, First );
strcpy( m_Last, Last );
m_Graduation = Graduation;
}
Student()
{
m_ID = m_Graduation = 0;
m_First[0] = m_Last[0] = '\0';
}
int GetID() cons
{
return( m_ID );
}
};
';
}
int GetID() cons
{
return( m_ID );
}
};

Besides the Student class, we ll also require another class to represent a course within the application. We ll call this class Course . The Course class contains all the information about a specific course, such as the course name , the course ID, and the instructor for the course, as shown in the following class definition. A course, however, does not contain students. Students are enrolled and attend a course, but are not actually inside the course.

 class Course 
{
protected:
char m_Name[50];
int m_ID;
public:
Course()
{
m_Name[0] = '
 class Course 
{
protected:
char m_Name[50];
int m_ID;
public:
Course()
{
m_Name[0] = '\0';
m_ID = 0;
}
virtual void Display ()
{
cout << "Course Name: " << m_Name << endl;
cout << "Course ID: " << m_ID << endl;
}
virtual void Modify( const char* Name, int ID )
{
strcpy( m_Name, Name );
m_ID = ID;
}
int GetID() const
{
return( m_ID );
}
};
';
m_ID = 0;
}
virtual void Display ()
{
cout << "Course Name: " << m_Name << endl;
cout << "Course ID: " << m_ID << endl;
}
virtual void Modify( const char* Name, int ID )
{
strcpy( m_Name, Name );
m_ID = ID;
}
int GetID() const
{
return( m_ID );
}
};

As you ve probably realized, something is missing from the Student class and the Course class. The Student class makes no reference to courses being taken by a student. Likewise, the Course class makes no reference to students who are taking the course. Therefore, there isn t any way for a student to register for a course and for a course to produce a roster of students who are taking the course.

We fill this gap by defining another class that links students with courses. We ll call this the LinkCourseStudent class. Its definition is next . Notice that the LinkCourseStudent class relates one student to one course rather than list all students registered for a particular course. We do this because we ll be declaring an array of instances of the LinkCourseStudent class. Each array is a course, and elements of each array are students enrolled in the course. The array is declared in the Enrollments class, which is described here:

 class LinkCourseStudent 
{
protected:
int m_StudentID, m_CourseID;
char m_Grade;
public:
LinkCourseStudent()
{
m_StudentID = m_CourseID = 0;
m_Grade = '
 class LinkCourseStudent 
{
protected:
int m_StudentID, m_CourseID;
char m_Grade;
public:
LinkCourseStudent()
{
m_StudentID = m_CourseID = 0;
m_Grade = '\0';
}
virtual void Modify( int StudentID, int CourseID,
char Grade='\0' )
{
m_StudentID = StudentID;
m_CourseID = CourseID;
m_Grade = Grade;
}
virtual void Display()
{
if( m_Grade != '\0' )
cout << "Grade: " << m_Grade << endl;
else
cout << "Grade not assigned" << endl;
}
bool operator==( const LinkCourseStudent& Src )
{
if( m_StudentID == Src.m_StudentID &&
m_CourseID == Src.m_CourseID )
return true;
else
return false;
}
};
';
}
virtual void Modify( int StudentID, int CourseID,
char Grade='
 class LinkCourseStudent 
{
protected:
int m_StudentID, m_CourseID;
char m_Grade;
public:
LinkCourseStudent()
{
m_StudentID = m_CourseID = 0;
m_Grade = '\0';
}
virtual void Modify( int StudentID, int CourseID,
char Grade='\0' )
{
m_StudentID = StudentID;
m_CourseID = CourseID;
m_Grade = Grade;
}
virtual void Display()
{
if( m_Grade != '\0' )
cout << "Grade: " << m_Grade << endl;
else
cout << "Grade not assigned" << endl;
}
bool operator==( const LinkCourseStudent& Src )
{
if( m_StudentID == Src.m_StudentID &&
m_CourseID == Src.m_CourseID )
return true;
else
return false;
}
};
' )
{
m_StudentID = StudentID;
m_CourseID = CourseID;
m_Grade = Grade;
}
virtual void Display()
{
if( m_Grade != '
 class LinkCourseStudent 
{
protected:
int m_StudentID, m_CourseID;
char m_Grade;
public:
LinkCourseStudent()
{
m_StudentID = m_CourseID = 0;
m_Grade = '\0';
}
virtual void Modify( int StudentID, int CourseID,
char Grade='\0' )
{
m_StudentID = StudentID;
m_CourseID = CourseID;
m_Grade = Grade;
}
virtual void Display()
{
if( m_Grade != '\0' )
cout << "Grade: " << m_Grade << endl;
else
cout << "Grade not assigned" << endl;
}
bool operator==( const LinkCourseStudent& Src )
{
if( m_StudentID == Src.m_StudentID &&
m_CourseID == Src.m_CourseID )
return true;
else
return false;
}
};
' )
cout << "Grade: " << m_Grade << endl;
else
cout << "Grade not assigned" << endl;
}
bool operator==( const LinkCourseStudent& Src )
{
if( m_StudentID == Src.m_StudentID &&
m_CourseID == Src.m_CourseID )
return true;
else
return false;
}
};

You will see in the definition of the LinkCourseStudent class that it contains three attributes or data members : a Student ID, a Course ID, and a grade. Therefore, one LinkCourseStudent object can be used to link one student to one course. It also contains the student s grade if available. In database terms, this would be referred to as a join or link table, which essentially permits a many-to-many relationship between students and courses (that is, many students to a course, and many courses to a student).

The Display method of the LinkCourseStudent class only displays the grade for the given student in the given course. The student s name could be printed using the Student class s Display method, and the course name could be printed using the Course class s Display method.

The Enrollments class is an array of all LinkCourseStudent objects. As a design issue, we could have stated that either the Course class maintains its own enrollments for its students or that the Student class maintains its own enrollments of classes. However, in keeping with a typical database design, where the links between all Student objects and all Course objects is maintained in a separate table, we will use the Enrollments class, shown here, to manage the link between students and courses:

 class Enrollments 
{
protected:
vector< LinkCourseStudent > m_Links;
public:
bool Find( const Student& S, const Course& C )
{
return Find( S.GetID(), C.GetID() );
}
bool Find( int sID, int cID )
{
bool isRegistered = false;
LinkCourseStudent Tmp;
Tmp.Modify( sID, cID );
for( int i=0; i < m_Links.size(); i++ )
{
if( Tmp == m_Links[i] )
{
isRegistered = true;
break;
}
}
return( isRegistered );
}
bool Register( const Student& S, const Course& C )
{
if( Find( S, C ) )
return( false );
LinkCourseStudent Tmp;
Tmp.Modify( S.GetID(), C.GetID() );
m_Links.push_back( Tmp );
return( true );
}
};

The last class we ll need is the Registrar class. The Registrar class is the catalyst that enables the student to register for a course and for the course to develop a roster. Here is the Registrar class definition:

 class Registrar 
{
protected:
// 'vector' is an array-type collection class
vector< Course > m_Courses;
vector< Student > m_Students;
Enrollments m_Enrollments;
public:
void AddCourse( const char* Name, int ID )
{
Course aCourse;
aCourse.Modify( Name, ID );
m_Courses.push_back( aCourse );
}
void AddStudent( int ID, char First[], char Last[],
int Graduation )
{
Student aStudent;
aStudent.Modify( ID, First, Last, Graduation );
m_Students.push_back( aStudent );
}
Course GetCourse( int ID )
{
for( int i=0; i < m_Courses.size(); i++ )
if( m_Courses[i].GetID() == ID )
return( m_Courses[i] );
return( Course() );
}
Student GetStudent( int ID )
{
for( int i=0; i < m_Students.size(); i++ )
if( m_Students[i].GetID() == ID )
return( m_Students[i] );
return( Student() );
}
bool Register( int StudentID, int CourseID )
{
int TheStudent, TheCourse;
for( TheStudent = 0; TheStudent < m_Students.size();
TheStudent++ )
if( StudentID == m_Students[TheStudent].GetID() )
break;
for( TheCourse = 0; TheCourse < m_Courses.size();
TheCourse++ )
if( CourseID == m_Courses[TheCourse].GetID() )
break;
if( TheStudent == m_Students.size()
TheCourse == m_Courses.size() )
return( false );
if( m_Enrollments.Register( m_Students[TheStudent],
m_Courses[TheCourse] ) )
return( true );
else
return( false );
}
};

The Registrar class is the class that ties together all the other classes in our example. Although it is a simplified view of a registration process, it is functional for the purposes of our example. The Registrar class maintains an array of Student and Course objects as well as an Enrollments object, which in turn maintains a link between students and the courses in which they are enrolled.

It is possible for the Registrar class to have students who aren t enrolled in any classes, or courses without any students (just think, when a class is first added to the system, it starts out empty). The Registrar class is designed to mimic the registration process and to manage students. In this class, you will find the AddStudent , AddCourse , GetStudent , and GetCourse methods , which permit you to add and retrieve students and courses to and from the system.

Once students and courses are added, you can then begin registering students for courses. You can also add students, register them, then add other students and register them as well. However, our demonstration will not show this approach for the sake of simplicity.

The Registrar class also provides a Register method, which is used to register a specific student with a course. Diagramming the collaboration between classes is usually viewed as a small part of the entire application (described in more detail next), and an application may have many such diagrams. The sample diagrams we present later on apply specifically to the Register method of the Registrar class.

UML Sequence Diagrams

Ever see a map of the universe? Probably not a detailed one because a detailed map would simply be too large and complicated to represent with any level of detail on a single piece of paper. The way things really work is that a map has enough detail to get you to another smaller, more-detailed map. So, we can see using a map of the United States to locate New York, and then using a map of New York to locate New York City, and then a map of New York City to locate Central Park.

When we use diagrams to represent programs, the same logic holds true. It would be too complicated to represent an entire program with any level of detail in a single diagram. As we show diagrams here, you will have to remember that they represent a different scale or level of detail, typically broken into a specific feature of the application.

A UML (Unified Modeling Language) sequence diagram is a diagram that shows the typical sequence of events for a process. It is not a flow control diagram, which also contains conditional logic for branching, such as an if or while test.

Sequence diagrams are organized as a set of columns , where each column is an object and shows (using arrows) how each object interacts with the other. At the top of each column, a class name and optional object name (for use in the diagram) are used in the format Object: Class. Down the columns, arrows are used to represent a method called by one of the objects to identify the collaboration between the objects. If a method needs an object parameter, that s when we give one of the columns the optional object name.

The arrows in a sequence diagram are solid lines for method calls, and dashed lines for their optional return values. The columns or lines descending from the class names are called life lines and show the life span of the object.

Is main() a Class?

The UML sequence diagram is useful to represent how classes interact. This raises an interesting question with C++: Is main() a class? (The answer is, of course, no; it s a function.) The reason this question might be asked is, if the sequence diagram represents interactions between classes, then what interacts with that interaction?

In other words, if we have a diagram showing how to register a student, then what called the StudentRegister method that the diagram represents? That code is probably in a menu somewhere. Okay, so what interacted with that menu? Well, if the language is C++, there s a good chance it was main() .

And therein lies the question: If main() is a method and not a class, how can we show the interaction of something that isn t a class using a sequence diagram? The answer is, we wouldn t. Ideally, the C++ program s main() method would actually just be invoked to create some sort of RegistrationApp class that is, in essence, the main application object. Using this, we can safely ignore main() .

This doesn t come up as often in Java or C#, where everything must reside in a class.

Student Registration

Let s use our new classes and work on some nifty diagrams. We will start by defining the classes that represent the registration process: Student , Course , Registrar , and Enrollments , as shown in Figure 9-1.

click to expand
Figure 9-1: A diagram of the previously described classes

The process of registering for a course begins at the point where the registrar has already been asked to register a student ( aStudent ) for a course ( aCourse ). Here s what happens next:

  • The registrar first checks whether the student is already registered by asking the Enrollments object to find a specific LinkCourseStudent using the Find function.

  • The Enrollments object asks the aCourse object for its unique ID, which is returned as cID .

  • The Enrollments object asks the aStudent object for its unique ID, which is returned as sID .

  • The Enrollments object then does a search of its own LinkCourseStudent objects for one that has these two IDs ( cID and sID ).

  • The Enrollments object then returns a Boolean value called isRegistered if the particular item is found.

We are only outlining one small section, which is to make sure that the student isn t already registered. The Registrar object would go on to interact further with the Enrollments object to make sure the class isn t full, then with the Student object to collect funds, and then with the Enrollments object again to add the student to the course. Figure 9-2 shows the completed sequence diagram for our registration process.

click to expand
Figure 9-2: The completed sequence diagram of the registration process

UML Collaboration Diagrams

UML collaboration diagrams are used to display basic interaction with classes. Whereas the sequence diagram shows a detailed step-by-step collaboration between objects, the collaboration diagram is more a simplified, bird s-eye view of the collaboration.

Collaboration diagrams merely need to show the objects and their basic connections. You can optionally include the methods that actually perform the collaboration, and their sequence, by listing the methods next to the connecting lines with a number indicating their sequence. Of course, if you do this, what you would typically accomplish is a very complicated diagram that s best suited for the sequence diagram.

Figure 9-3 shows the collaboration diagram for our previous example. It shows that the Registrar class interacts with the Enrollments class, but not directly with the Course and Student classes. The Enrollments class, however, interacts with the Student and Course classes.

click to expand
Figure 9-3: A UML collaboration diagram

Messages

When discussing collaboration, we typically say that a class collaborates with another via a message . For example, the Registrar class sends the Enrollments class a Find message to see whether a student and course are already registered.

Although message is a wonderful design and analysis term , as a programmer you have to keep in mind that a message is really just a function call. When we say that Registrar sends a Find message to Enrollments , more technically what we are saying is that the Registrar class will call the Find method in the Enrollments class.

By designing the sequence of collaboration before the classes, we can help identify what methods will be needed in what classes, as well as what they need to do, what they will accept as parameters, and what they will return as optional return values. For example, we now know that the Enrollments class needs a Find method.

The use of the term message to refer to a method call is really nothing new. In Windows programs, we often refer to Windows sending a paint message to a window so that it can redraw itself (if, for example, it was behind another window that was just closed). In Windows programming, applications have a WndProc (or window procedure) function, which the Windows operating system calls with the given message. The C prototype for this function looks like the following:

 LRESULT WndProc( HWND hWnd, UINT Message, 
WPARAM wParam, LPARAM lParam );

The exact implementation of this function isn t important, but look closely at the second parameter: Message . When Windows wants an application to redraw itself, it finds the WndProc for the given main window (don t ask how Windows knows where it is, that s its job) and calls the WndProc , passing WM_PAINT as the second parameter.

This provides an interesting way of handling messages: Although we stated that a message is simply a function call, it is possible that several messages may be handled by the same function. As long as the information as to what message is being sent is passed to the method, it can handle any number of messages.

Collaboration and Inheritance

Collaboration in itself is not closely related to inheritance, but it can raise some interesting questions about design vs. coding. For example, we have an Enrollments class that we never saw before. That class is really nothing more than a collection class of some other type.

The Enrollments class might in fact be a vector class in C++, Java, or C# (except that we specifically use the Find method). So, if the Enrollments class is really one of these other classes, why not show that class name instead? Well, because Vector isn t quite as descriptive as Enrollments .

It s more likely that the Enrollments class is derived from the vector class, with some additional functionality added. The problem is, we have two people, or we wear two hats: the designer and the programmer.

The designer might say, I want an Enrollments class, but the programmer might say You already have one; it s called Vector . Although code reuse is always important, don t lose sight of your goal: to design, build, and deliver a working application.

You would most likely choose to have the Enrollments class derived from the vector class and provide the additional features as needed. You could opt to use the optional Object: Name in the sequence diagram, such as Enrollments : Vector , but just the mention of the name vector might cause confusion on a design scale.

Association

Which came first, the chicken or the egg? We could ask the same question about association and collaboration. Association simply means that a class some how interacts with another class. Well, if they interact, then they collaborate. In order for there to be collaboration, there must be association.

This association means that the two classes need to know how to interact with one another. They need to know the other s methods, return values, and so on. We aren t breaking the encapsulation rules here, because the two classes don t need to have detailed knowledge of how the other works. They just need the basics, the interfaces.

This is normal methods and accessor methods come into play. Class design isn t just need a class, design a class. Using our diagrams and understanding collaboration both contribute to the design of a class. As we see the class might need to collaborate with other classes, we add more functionality or interfaces.

If we clearly define the collaboration for all our classes, and therefore their association, we can create a clear definition of the class needed. With few exceptions (particularly in the area of testing and debugging methods), any methods not used in a diagram should not be added to a class.

It would by typical to start designing a class by thinking, Okay, what does this class need? However, that would probably be a mistake. You can t define the class on its own unless it s a very generic class such as a string or vector class. You have to know exactly how a class fits into the scheme of the application, and how it will be used, before you can start designing the class.

Avoid adding methods to classes just in case. Wait for a need to arise and then add them. This need, of course, should arise during the design phase.

Self-Collaboration

Objects can collaborate with themselves as well as other objects. In our registration example, note how the sequence diagram shows a circular arrow that states Find. This means that the Enrollments class calls its own Find method after getting the ID of both the student and course.

Although it is sometimes helpful to just point this out, it can also lead to the design of a given class. For example, we discussed in previous chapters that classes typically have private, public, and protected data members. Though not always the case, we might consider that self-collaboration methods are protected or private methods in a class, not accessible from the outside world.

Self-collaboration can occur as a single method or as multiple methods. Consider the following scenario: The Enrollments objects needs to save itself to disk. In order to do this, it would probably do something such as save the count of elements it has to disk and then save each element ”something like the following pseudo code:

 void Enrollments::Save( File ) 
{
int I;
WriteInt( File, GetCount() ); // Write count of elements
for( i=0; I < GetCount(); i++ )
{
GetAt(i).Save( File );
}
}

You can see that the Save method of the Enrollments class is calling its own GetCount and GetAt methods to save the data to disk. The Enrollments class s Save method calls the Save method for each of its elements. Note that to implement this functionality, our demonstration classes would all need to be modified to include Save and Load methods. To keep things simple, the preceding code is pseudo code and not actually implemented in the Enrollments class described or the sample source code for this book.

Class Responsibility Collaborator

As you saw in the UML Sequence Diagram section, sequence diagrams typically show a small part of the detailed interaction between two classes. A typical system would have many such diagrams, and the diagrams themselves might even be to different scales to describe certain degrees of abstraction.

The Class Responsibility Collaborator (CRC) diagram is intended to be an all- inclusive list of how a single class is to be designed. As you create and maintain your sequence diagrams, you will also want to maintain your CRC diagrams as well.

A CRC diagram lists the class name at the top and then two columns below for responsibilities and collaborators. It does not make an attempt to define the collaboration itself, such as what methods are called. Figure 9-4 shows a CRC diagram for our Enrollments class.

click to expand
Figure 9-4: The CRC diagram for the Enrollments class

As you can see from Figure 9-4, the Enrollments class maintains a collection of LinkCourseStudent objects and provides methods to add, find, and delete these objects. It also interacts with the Course and Student classes.

The Enrollments class doesn t collaborate with the Registrar object because it doesn t call methods within the Registrar class. Although it is possible to define the collaboration with the Registrar class, the diagram could quickly become complex. At some point, we need to say that the collaboration stops at some level.

We also wouldn t diagram items in the CRC card that weren t directly collaborated with. For example, in Figure 9-2, the Student class doesn t directly interact with the Course class. Therefore, we wouldn t indicate this as a collaborator on the Student CRC.

Note  

CRC diagrams are often printed as a card and referred to as CRC cards. These cards can be helpful for the brainstorming process of the design.




OOP Demystified
OOP Demystified
ISBN: 0072253630
EAN: 2147483647
Year: 2006
Pages: 130

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net