| |
Main Menu | Next Section |
This ends the analysis phase and we proceed to design. In chapter three we saw that design involved determining the purpose, receives, returns, and algorithm for each function. We will use much the same approach here. Each behavioral responsibility of a class is represented by a member function of the class. Our first task then is to design these functions, that is, determine the purpose of each function, what each one receives and returns, and, if necessary, write an algorithm for the function.
There is an additional part to class design. At this point we have listed the knowledge responsibilities of each class. In our case, we have also decided which knowledge responsibilities will be implemented as properties. (See the discussion of 'Provide Per Week Charge' in the previous section.) Under other circumstances but we would not have decided this yet and would have to do so now. In either case, we still need to decide how to implement the properties. When implemented in a class declaration, properties are often referred to as data members since they are data belonging to instances of a class. We will start using the terms property and data member interchangeably. We will see that designing data members can be quite complex. For now, however, it is simply a matter of determining the type (for example, int, double, or char) of each property/data member.
In complex programming problems the design stage usually is kept separate from any coding. Here, we will blur the distinction a bit by discussing class design in the context of declaring a class.
A. Classes as Types
This ends the analysis and design phases. It is now time to begin
coding. Before we do so, however, let's clarify the meaning of
and the relationships among the words type, class, object, and
instance. From the earlier chapters we know what a type is in
C++. A type is a name for some category or classification. Integer
is a type; 3 is an instance of the type integer. We have used
this idea of type when we declared a variable to be of some type,
for example:
Classes can be seen as complex types - they certainly name some category or classification. A class then is nothing but an idea, a concept, a description. In a program a class only represents the possibility of a certain type of object. It is the instances of a class that represent actual objects. Thus, only instances of a class can be used/manipulated in a program.
The process is that
a programmer first declares a class (describes the properties
and behaviors of the class). He or she then defines the behaviors
of the class (remember the difference between declaring and defining
a function). And finally, he or she declares instances of the
class.
The interesting thing about a class (or type) is that one can imagine a class that does not have any actually existing
members. Most of us can imagine the class "hairy monsters
with three heads that live on the far side of the moon".
It is doubtful, however, if instances of such a class exist.
B. Class Declarations and Encapsulation
Up to now we have only worked with the built-in C++ types, for
example, int, double, and char. We have also worked with many
of the built-in operators (the arithmetic and relational operators)
and statements such as 'while' and 'if'. If we were limited in
C++ to using the built-in functions and types, the language would
be powerful but awkward to use. Using function declarations and
definitions, however, we have moved one step farther in terms
of statements and have actually created our own additional statements
because a function call can be considered a statement - it tells
the system to do something. What it tells the system to do, of
course, is the set of instructions in the function definition.
Therefore, when we write functions we are extending the instruction
set of the language! We need to be able to do the same with types.
In the last section we introduced the idea that a 'class' is a
type. In this section we explore this idea further and study one
way to extend the set of types built into C++ - by declaring classes.
To make our discussion concrete we will continue with the class
'contract'.
To declare a new class one starts with the word 'class' followed by the class's name and a set of curly brackets to indicate that all that follows is related to the named class. Oh, and don't forget the semicolon at the end - when forgotten, some strange compiler errors can result.
Back when
the object oriented paradigm was first introduced we
talked about the idea of encapsulation - that the data members
(properties) of a class are usually not directly accessible to
the rest of the program. We hinted that it would
be the member functions that would provide the access. We also
stated that the member functions of a class would implement the
behaviors of the class, the behaviors that would be used by other
parts of the program. These functions must, therefore be accessible
outside the class and its instances- or how then could they be
used.
The first thing to understand then is that classes have public
and private parts. Those elements of a class that are
to be encapsulated in the class are declared in the private part.
It is this 'private' declaration that causes the data members - and their organization or structure - to be encapsulated. (Our simple data members here do not have any organization or structure to speak of. Later, however, in Chapter 8, Section IV we will see examples of structure.)
In almost all cases the data members are private to a class. Access is only through a set of member functions (the interface) whose specific tasks are to provide or change the values of the data members of a class. To make these functions accessible they must be included (declared) in the public part of a class. In other words, any member function that represents a responsibility of the class/object or has the sole purpose of allowing access from outside the object to the member data of an object must be declared in the public part of the class. As noted earlier, such functions are called interface functions and you should make sure you include all necessary interface functions in your class design.
All this means that class declarations are complex and one might
want to ask again why have encapsulation! After all, shouldn't
I as a programmer be able to change the value of any variable/data
member that I want to from anywhere in a program ? From one perspective
the answer to this question might well be 'yes' - if programmers
were perfect and never made mistakes. Unfortunately, they aren't
and encapsulation helps avoid mistakes. It also both allows programs
to be more easily modified and supports team programming. A programmer
can quickly devise a class, complete with the necessary interface
functions and give it to others to use. The programmer can then
go back and improve on the class while the other team members
write code based on the class interface. As long as the programmer's
improvements do not change the interface, the modifications have
NO effect on the code written by those using the class. If the
other team members had direct access to the insides of the class
and took advantage of the internal structure, any changes made
to that structure would require changes in other team member's
code.
This is the same way that a car works. One can have the engine,
the carburetor, the exhaust system etc. changed and still not
need to re-learn how to drive since the interface (steering wheel,
pedals ...) are all the same.
Back to the design: member functions also can be private. Clearly
the interface functions must be public since they must be accessible
to any code that wants to manipulate an instance/object of a class.
Private functions are those functions that are known only by the
objects of a class and therefore they cannot be used outside the
objects. Usually such functions exist because the programmer decides
that some member function is too complex or has elements that can
be re-used in other member functions. He or she then decomposes
the member function as in chapter four. It is these sub-functions
that are inaccessible outside the object and therefore are private.
Since classes have public and private parts, the code for our
'contract' class must reflect this:
The words 'public' and 'private' could be interchanged but most
experienced programmers prefer the order we have chosen. This
encourages the focus to be placed on the public part of the class
as opposed to the private part. Since the public part of the class
comes first and the member functions are public, let's first focus
on designing and declaring them. Here is where we really use the
material from the earlier chapters so make sure you are comfortable
with function design, declaration and definition.
The member functions that we have determined need to be included in
the class 'contract' are:
C. Declaring Functions that Change the Value of a Property
There are three functions whose task it is to change the value
of one of the properties of an instance. The design for them is
all essentially the same so we will carefully walk through the
design of only one, 'Change Square Footage'. As so
often is the case, the purpose is obvious from the name, as is
the goal state. All that is left is
to decide what is received and returned. So, what does this function
need in order to accomplish its task? And, the answer is (trumpet roll):
the new 'square footage' value. Finally, does this function need
to return anything to the function that called it? No, for the
same reason that output functions do not return values usually.
Here then is our design:
ChangeSquareFootage
Purpose: To change the value of the square footage data member
Goal: The square footage data member has a new value
Receives The new Square Footage (an integer)
Returns: NONE
The design for the other two 'change property' functions are exactly
the same and here we have another pattern. All member functions
whose purpose is to change the value of some property of a class
have the same design. They also have the same declaration form
as is shown below:
class Contract
{public:
void ChangeSquareFootage(int sqFootage);
/*
Purpose: To change the value of the square footage data member
Goal: The square footage data member has a new value
Receives The new Square Footage (an integer)
Returns: NONE
*/
void ChangeNumDesks( int numDesks);
// Design comments go here.
void ChangeNumDays( int numDays);
// Design comments go here.
.
.
private:
.
.
};
First, the function name gives away the purpose and goal of the
function. Now, however, the receives and return analysis is backwards. These
'provide ...' functions do not need anything from the calling
function in the same way that a function that gets a value from
the user does not have any 'receives'. Member functions have access
to all the member data in the instance of the class they are associated
with. Therefore, 'ProvideSquareFootage', for example, has access
to the square footage of any instance it is associated with. (The
meaning of 'associated with' must be kept vague for the moment.
When we discuss how to use these functions, this will be come
clearer.)
With regards to the return analysis: just like a function that
gets a value from a user, these functions return one value - the
value found in the property they are responsible for. Here then
is the design for the first of these functions and the declarations
for all three - added after the code discussed above.
class Contract
{public:
void ChangeSquareFootage(int sqFootage);
/*
Purpose: To change the value of the square footage data member
Goal: The square footage data member has a new value
Receives The new Square Footage (an integer)
Returns: NONE
*/
void ChangeNumDesks( int numDesks);
// Design comments go here.
void ChangeNumDays( int numDays);
// Design comments go here.
int ProvideSquareFootage();
/*
Purpose: To Return the Square Footage of an Office
Goal: The Square Footage is returned
Receives: NONE
Returns: the Square Footage (an integer)
*/
int ProvideNumberOfDesks();
// Design comments go here.
int ProvideNumberOfDays();
// Design comments go here.
private:
.
.
};
It should also be observed that the decision to always calculate
or not is irrelevant to any function that uses this function.
All that matters to any function that needs the per week charge
is that the charge is delivered correctly. Therefore, we could
later decide to change our mind and have a class property that
holds the per week charge and carefully update it when needed.
Nothing in the rest of the code would need to change. Here again
is an example of the power of encapsulation and function decomposition.
Changes to one part of a program can be made with little or no
effect on the rest of the program.
So, now we have a function called 'ProvidePerWeekCharge' whose
purpose is not quite specified by its name. Usually we want a
name that indicates exactly what the function does but in this
case we have chosen a name that indicates to the user some of
what happens while not telling the whole story - and that is OK!
In the Purpose and Goal statements, however, we to want to state
clearly what the function does:
ProvidePerWeekCharge
Purpose: To calculate and return the per week charge for an
office
Goal: The per week charge is returned
We proceed then to the questions involving receives and returns. First, "what does this function need to accomplish its task?" The answer to that is found in the formula:
:
Clearly, the calculation cannot be accomplished unless the function
has the Square Footage, the Number of Days Per Week, and the Number
of Desks. However, where does it get this information from? To
answer this, review where 'ProvideSquareFootage' gets the square
footage from. The key here is that all member functions have access
to all member data of the instances they are associated with.
Thus, the member function 'ProvidePerWeekCharge' has access to
the square footage, number of desks, and number of days data for
whichever instance the function is associated with. The conclusion
then is that 'ProvidePerWeekCharge' has no 'receives' because
it has access to all the information it needs.
This is an important point that has been mentioned twice and could bear to be mentioned again.
In C++ terms: If we think of the "decision function" mentioned above as a member function of the class 'Person' then we can say that it has access to Fred's age because Fred is an instance of person'. This member function does not, however, have access to the state (the values of all the properties) of the store since the store is not an instance of the class 'Person'.
End of Analogy! Now, what are the returns? If someone asked you to perform some
calculation, what do you think they would want back? The result
of the calculation, of course! And, that is what this function
will return, the calculated weekly charge. Since a 'charge' is
a dollar amount, the result is of type double. The design and
declaration for this function should be inserted just after the
declaration for 'ProvideNumberOfDays' in the declaration for the
class 'contract'.
Receives: NONE
Returns: The Per Week Charge (a double)
*/
This completes the declaration of all the member functions for
this class and all the elements of the public part of the class.
E. Declaring Member Data - the elements of the Private Part
of a Class
As stated above, usually data members are placed in the private part of the class to encapsulate them in hopes of carefully controlling changes. A reminder: the properties of an instance of a class and the data members are the same. Each instance has its own set of property values and thus its own data members. Each data member is really a memory location with the property name being the symbolic name for that memory location. In other words, data members are variables with restricted access. (A third name for data member or property is "instance variable.")
In our case we have three data members. Based on our interface analysis (not explicitly done here) and on common sense, it seems logical to decide that all three will be of type 'int'.
We have seen how local variables belong to and only exist when the function they are local to is called. Data members are variables that belong to and only exist when instances of the class exist . When we declare the member data (and the member functions) of a class we are describing in general terms the properties and behaviors of instances of the class. However, we are NOT creating any specific instances - just as when we describe how "monsters with three heads that live on the far side of the moon" might look and behave, we are not actually creating any such monster. We will see in a bit how to create instances of a class.
The declaration for data members is exactly the same as for any
other variable declaration. It should be noted that this time
we are dealing with pure declarations and not declarations combined
with definitions since memory is not set aside for the data members
until an instance is created. Also, note that we have as many
memory locations set aside for a certain property declared in
a class as there are instances of the given class. In other words,
if there are ten contracts then there are ten versions of each
of the data members and therefore, for example, there are ten
memory locations for square footage.
Since our specifications and design have pretty much determined
the type for all the data members, the actual declarations are
easy. Here is the completed declaration for the class 'contract'.
// Declaration of the class Contract
// File: contract.h
class Contract
{public:
void ChangeSquareFootage(int sqFootage);
/*
Purpose: To change the value of the square footage data member
Goal: The square footage data member has a new value
Receives The new Square Footage (an integer)
Returns: NONE
*/
void ChangeNumDesks( int numDesks);
// Design comments go here.
void ChangeNumDays( int numDays);
// Design comments go here.
int ProvideSquareFootage();
/*
Purpose: To Return the Square Footage of an Office
Goal: The Square Footage is returned
Receives: NONE
Returns the Square Footage (an integer)
*/
int ProvideNumberOfDesks();
// Design comments go here.
int ProvideNumberOfDays();
// Design comments go here.
double ProvidePerWeekCharge ();
/*
Purpose: To calculate and return the per week charge for an office
Goal: The per week charge is returned
Receives: NONE
Returns: The Per Week Charge (a double)
*/
private:
int squareFootage;
int numberOfDaysPerWeek;
int numberOfDesks;
};
That finishes the analysis and design phases. It is now time to
focus on the how. If we have done our work well, it should be
easy to define each member function. In the next section we will
test this theory and look at the specifics of member function
definitions.
| |
Main Menu | Next Section |