|
Table of Contents
Learning C++:
An Index of Entry Points
2. The A reference document on the basic elements of C++.
3. The Patterns
|
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
double thisNumber;
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
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
ChangeSquareFootage
Receives The new Square Footage (an integer)
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:
D. Declaring Functions that Return or Provide the Value of a
Property
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.
There is still one function to go - 'ProvidePerWeekCharge'. We
could call it 'CalculatePerWeekCharge' because we decided
above to calculate the value each time it is requested.
Note, however, that the purpose of the function is to provide
the per week charge, regardless of whether we calculate the value
each time or not. Thus, the first name is more accurate.
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
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.
An analogy: Fred is a person standing outside a store with a sign that
says, "No one under twelve allowed inside unless accompanied by an adult."
Whatever 'function' Fred uses to decide whether he can enter the store by
himself does not need to 'receive' Fred's age. He knows his own age. Now
suppose Fred is eleven years old and the sign says, "No more than three
people under twelve allowed inside unless accompanied by an adult." Fred
may well not know how many people are already inside the store and, in
this case, his 'decision function' will need to somehow receive or ask for
this information.
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
*/
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
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'.
F. A Quick Review
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. |