cs1ch5.htm
 
CHAPTER 5

Object Oriented Design



  


Table of Contents

Learning C++:
An Index of Entry Points


2. The

of C++

A reference document on the basic elements of C++.



3. The Patterns





I. Introduction
In the last chapter we saw how a problem could be viewed as a set of tasks to be accomplished. Complexity management then became a process of decomposing the problem into tasks and subtasks until one arrived at a set of tasks that were independent from each other and easily grasped. Such tasks are, at least in theory, easy to analyze, design and code.

For many years this process of functional decomposition was the main tool programmers used to handle complexity. It made a certain amount of practical sense because, as we noted in chapter 3, humans do often handle task complexity by decomposing tasks into smaller tasks. More recently, however, a new approach for analyzing a complex problem and designing a solution has emerged. This new design methodology is based on the object oriented programming (OOP) paradigm (a paradigm is a pattern or way of looking at things). The object oriented paradigm says that the world can be best analyzed by focusing not on the tasks but on the objects found in the world.

Back in chapter one we argued that Computer Science can be seen as a science of modeling where we study approaches to modeling or capturing some element of our real or imaginary world(s) in a computer program. To create a computer model of some world we need to analyze and come to an understanding of that world. Perhaps then, it is worth studying how humans analyze and describe (create a model of) the elements of our existence. Although humans do perform functional decomposition when faced with a task to accomplish, they are much more likely to look at the world itself in terms of the objects (things) they see in that world.

Consider the idea of a school. While there are all kinds of actions being performed in a school, if you ask someone to describe what a school is, they are more likely to start with the objects that exist in a school: teachers, students, classrooms, tests, homework, etc. than the activities. Of course, part of the process of describing these objects includes stating what they do. It is not that actions are ignored. They are simply not the first aspects to be observed and commented on.

Proponents of the object oriented paradigm argue that programming problems can be best analyzed by focusing first on the objects involved in a problem and only then on the functions performed by and on those objects. It is not so much a matter of which is better but, simply of which comes first - the object analysis or the task analysis. Indeed, we shall see that all the work we did with functions in the last chapter will be very important in our efforts in this chapter.

The idea then is that when a programmer is given a new problem, the decomposition process that initially takes place is in terms of the objects found in the problem. These objects can be classified into different groupings called classes and these classes can then be analyzed to determine their properties, the actions they are required to perform, and their relationships with other classes. A specific object is an instance of a class and has all the properties, actions, and relationships determined for the class.

The notions of 'class', 'property' and 'instance' as found in the object oriented approach, are not new concepts. Humans have been decomposing/dividing the world up into object/classes for as long as they have been wondering about the world. One strong example of this is Biology where the complexity of life around us has been studied extensively in terms of classifying life forms into various groups and subgroups. The basic group is the species. Using the object oriented terms of class and instance, a species can be seen as a class and an individual member of the species as an instance of the class, an object belonging to the class. Thus, you as an individual are an instance (member) of the species (class) Homo Sapiens. You therefore have the properties and know how to perform the actions known to any instance of the class Homo Sapiens.

The strength of this approach for the study of life is that a biologist can focus on the commonalties found in all members of a species, for example, Homo Sapiens, and not worry about the idiosyncrasies of a particular instance of the species, as in the unique characteristics of a particular human. Same for the programmer - he or she can design a class by focusing on the common properties and actions of the class. Later, all instances of the class (objects belonging to the class) can take advantage of that design.

The OOP paradigm applies this notion to a specific programming task by trying to discover the objects involved in the programming task, the classes into which those objects can be grouped, and the relationships among classes. As part of this analysis the OOP paradigm seeks to determine what are the common properties (characteristics) of all instances of each class and what actions the instances of each class are capable of. From this, one can design the necessary classes.

The OOP paradigm goes one step further with its ideas of class and object. The properties of an object belong to the object itself and should not easily be changed by other objects. It is not that the properties can't be changed but the change should be controlled. From a programming perspective the idea of 'controlled changes' helps insure that variables in a program do not get changed except when such a change is really desired by the programmer. Local variables and parameter passing in functions were a first step to ensure 'controlled change' and the object paradigm extends this idea. Since your only experience with programming may be what you have done so far with this text, this issue may not be clear. If so, you need to trust that one way to support the production of 'bug free' code is to control when values get changed.

In the OOP approach 'controlled change' is implemented through encapsulation. What encapsulation means is that the properties of an object, as defined in the class, are 'inside' the object and cannot be easily accessed or changed by other objects. As we will see, to change encapsulated properties one tells the object itself that the change should be made and the object handles the actual change.

II. Class Analysis and Design
The object-oriented design process can be broken up into six steps:

  1. Discovering the objects necessary for the program and grouping those objects into classes;

  2. For each class determining the characteristics or properties of that class (what we will formally call the member data or instance variables of a class);

  3. For each class determining the actions, operations or responsibilities of the class (what we will call the member functions or methods of a class);

  4. Determining the relationships among classes;

  5. Declaring the classes and all member data and member functions necessary for each class;

  6. Designing algorithms for any member functions belonging to each class.

You have spent quite a bit of time already designing algorithms so you should have some sense of how to handle the sixth step. Remember to apply all that you have already learned as we proceed into this new territory. In this chapter we will spend a lot of time focusing on steps one through five as they are fundamental to a successful object-based programming effort. The key to these steps is that they describe what is required of the program without saying anything about how. It is important to not worry about how you are going to accomplish a task until you are sure you know what it is you are trying to accomplish. Even step six which does focus on the 'how' of each function member does so at a high level - the algorithmic level - and there is still no concern with syntactic details of a specific programming language.

Once the design of the various classes required for a program is complete, one still must determine how the instances of the various classes, the actual objects, are to be used. There must be some part of the program that coordinates the activities of the various objects. In our programs this coordinator will be the function 'main' (the same 'main' we have already seen and used) and the functions associated with it in the same file. The last phase of the design stage, therefore, involves designing 'main' and any functions it uses that are not part of specific classes.

This last part will differ only slightly from what we have done so far. One must determine the variables and functions required to actually use the objects found in the problem and design algorithms for any of those functions that are complex. Note that these functions do not belong to a specific class. For that reason they are referred to as 'non-member functions'. All the functions we have worked with so far have been 'non-member' functions. Since we were not using classes and had no idea what 'member' functions were, there was no reason to talk about non-member functions.

Sometimes the challenge in object-oriented analysis and design is in discovering the classes necessary for a program. Sometimes the challenge is in finding appropriate properties of the class(es) (What should an instance of class 'person' include - a name property? an age property? a date of birth property?, the grandfather's name? ....). Sometimes the challenge is in determining the correct set of actions (member functions/methods) the class(es) should be capable of handling. Most often the challenge is in all of these.

Although the five steps of the object oriented analysis and design process are described separately below, they often come together in the actual process of designing a program. And, as with functional decomposition, it is important that you complete the analysis and design before proceeding to the code.

In other words, when you first start the analysis and design process, be careful to focus only on what objects are involved and what the program is to do. Don't worry about the how of the programming problem and certainly don't ask yourself if you know how. All that comes later.

III. The Analysis Phase
The analysis process that we learned in chapter three remains the same with some refinements. The first step in that process was to write a narrative description of the problem. Make sure that this problem narrative is detailed enough and describes all the aspects of the programming problem. The new element of the process is to examine the problem narrative, underlining all important nouns and circling all important verbs.

The words you underline in the narrative represent possible classes, objects and member data as well as elements that are not part of any particular class/object. Our task is to decide which of the underlined nouns represent objects (or classes of objects),which nouns represent parts of objects/classes (the member data) and which nouns represent elements that are not part of any class.

For example, consider a problem narrative that includes the sentences:

    "Fred, John, and Mary are 3 of the total of 10 persons involved in the project. Each person has a name and address."

It would be logical, given our common sense understanding of persons, names, and addresses, to say that the word person represents a class , that Fred, John, and Mary are objects (instances of the class 'person'), and that name and address are properties (data members) of the same class. It would also be logical to say that 'total' is not part of the class 'person' but it may be an important part or element of the problem.

Not all cases are as easy as this one but you should use your common sense and understanding of the world you are trying to model (write a program for) to help you distinguish between objects, classes and properties of objects.

Note that a class is a general description of an object or set of objects. In the example above, the sentences talk about a number of individuals. We then have a number of objects but each can be represented by the same class, Person. Our goal then is to use the discovery of the objects that exist in a program to discover the classes necessary to represent all the objects involved.

The words circled in the problem narrative represent possible member or non-member functions. To find member functions look for verbs describing actions required of an object or class. As a general rule, these include actions that ask for, change, or use the values of the data members of a class.

IV. CRC Cards
The circled nouns and underlined verbs give us a starting point. What we need now is a systematic way of determining the objects/classes required of a program and what each object/class consists of. In this course we will use a team oriented approach called CRC - more on the meaning of these letters later.

In the CRC approach a design team gathers together with a stack of index cards. Using the underlined nouns in the problem narrative the team comes up with the objects/classes required in the program and writes down a name for a specific class in the upper left corner of a card - one card for each class. A line is then drawn down the middle of the card and on the left we use the circled verbs to determine the responsibilities of the class - what actions will objects belonging to this class perform. On the right side we list other classes that this class must use in order to carry out its responsibilities. Such required classes are called collaborators. Thus we see where the name CRC comes from - Classes, Responsibilities, Collaborators.

Notice the words 'responsibilities' and 'collaborators'. These have been chosen on purpose to emphasize that each object (instance of some class) is to be considered as an 'actor'. Objects 'do' things. They receive and respond to messages or requests sent to them by other objects or 'main' and its functions. This may seem a bit strange at first - as if we are anthropormorphizing our programs. For the fun and ease of it, you might want to imagine a program as some kind magical land where inanimate objects come to life.

Consider as an example, a race car game played on a computer. An object-oriented program for such a game would probably consist of a race track class with one or more object instances representing possible race tracks and a race car class with one or more instances representing the various cars involved in a race. We humans do actually use phrases like the 'response' of the car, and we can think of an instance of the class 'car' in this program as 'responding' to messages to accelerate or brake or turn. In other words, each instance of the class 'car' must be responsible for accelerating, braking, and turning itself. The responsibilities of an instance of race track might include displaying the race on the screen, keeping track of such things as who is in the lead and the elapsed time of the race, and of informing a car when it has crashed into a wall of the track.

This last may seem strange but again, remember the magic land we are in. Perhaps one should actually have a class wall where some set of instances of wall form part of an instance of track, and instances of wall inform instances of car when a car has crashed into a wall. Walls would be collaborators of track because a track would not know if there was a crash (something it needs to know if it is to accurately display the race) unless it asked the walls of a track. (If this seems a bit complex and far fetched, don't worry much about it yet. The details will come later.)

Back to our CRC cards, note that this approach does not directly determine the data members or properties of a class. The assumption, as we will see below, is that some collaborators (such as walls in relation to tracks) will become data members (tracks have walls) and that other data members will become clear as we consider which underlined words in the problem narrative are useful to the behaviors/responsibilities of the class.

V. Discovering the Classes Necessary for a Programming Problem

A. The Analysis Phase
What with walls sending message and all it's probably time for a concrete example before we really get crazy. The race car game we just talked about is too complex for the moment so let's look at a more boring but simpler problem:.

    Olympia owns a small office cleaning business. She wishes to have a program to keep track of the five contracts she presently has with various businesses. She charges her customers on the basis of the square footage of the office, the number of desks in the office, and the number of days per week the office is to be cleaned. The formula she uses is as follows:

    Weekly Charge = Days per Week * ($0.05 per square feet + $5 per desk)

    Of course, contract information can change so the program should be able to modify the contracts.

This problem description has been kept vague on purpose. Users rarely can describe their own problems in a way useful to the programmer. It is our job to tease out the information we need and hand it back to the user to make sure our analysis is correct. As a first step then, here is a problem narrative for this problem

    Olympia owns a small office cleaning business. She wishes to have a program to keep track of the five contracts she presently has with various businesses. Each contract involves one office complex and should keep track of the square footage of the office, the number of desks in the office, and the number of days per week the office is to be cleaned. No contract can exist that does not have all this information provided initially. A user of this program should be able to change any of this information and get the per week charge based on the formula:

    Weekly Charge = Days per Week * ($0.05 per square feet + $5 per desk)

This description is the same but more detailed than the original. Hopefully, enough detail has been provided to help us discover the classes involved, their properties, behaviors, and relationships. This is a simple problem so it is likely that we will succeed. Be aware that a more complex problem will require both a more detailed problem narrative and a more complex analysis of that narrative.

B. Part One of the Design Phase - Discovering the Classes
The rest of the analysis phase involves the interface analysis etc. You are urged to do this on your own. Here we will proceed to the process of discovering the objects and classes. To accomplish that, we need to underline the nouns and circle the verbs. Since it is not easy to show 'circled' verbs on a computer screen, we will use italics:

    Olympia owns a small office cleaning business. She wishes to have a program to keep track of the five contracts she presently has with various businesses. Each contract involves one office complex and should keep track of the square footage of the office, the number of desks in the office, and the number of days per week the office is to be cleaned. To initiate a contract all this information must be provided. A user of this program should be able to change any of this information and get all of it plus the per week charge which is calculated based on the formula:

    Weekly Charge = Days per Week * ($0.05 per square feet + $5 per desk)

In this example all nouns and verbs have been underlined or italicized the first time they appear. Since the underlined and italicized words are to be used as clues in discovering the necessary classes etc., it is not necessary to 'mark' clues that have already been discovered. Likewise, words that really refer to the same thing, as in "office complex" and "office" are not both 'marked'.

Many of the words here really are irrelevant to the process and can be weeded out immediately. Our goal is to create a 'model' of the contract world of Olympia. Since concepts such as 'program' and 'user' (of the program) are not part of that world, they are not part of the model and can be ignored.

Perhaps not as obvious, 'Olympia' and 'business' can also be ignored. The task here is to discover those objects that are part of Olympia's business world. Olympia herself owns that world. She is not contained in that world. Likewise, the business world is what is being modeled, it is not part of itself. Any words in a problem narrative that name the world being modeled or the 'owner' of that world can be ignored. It is not that these words could be left out of the problem narrative. They are necessary to describe the problem but they are not useful as clues in finding the classes and class properties.

The nouns that are left include:

contract square footage     number of desks
number of days     information per week charge
formula office complex

Now we need to determine which of these represent classes, which represent objects, which represent properties of objects, which represent other elements of the program, and which can be ignored. Let's start with contract. It certainly is a thing, an object and it doesn't sound like a property or a description of some other object. Contracts seem to 'exist' by themselves in the little world we are modeling. As a matter of fact, there are five of them which means that there are five instances of contract. This is a sign that we might have a class. Further, each individual contract is unique but it is likely that they all have characteristics in common - another sign that we have a likely candidate for a class. Let's consider it a class for the moment.

How about 'square footage'. By the way it is written, it is clear that it is meant to describe a characteristic of a contract - contracts have a square footage associated with them. Therefore 'square footage' is a property of the contract class. This gives us more confidence that 'contract' is a likely class - we have found a property for it. Classes without properties (or behaviors) are of little use in a program.

Next comes 'number of desks'. Here, perhaps, we cheated in our initial underlining. 'Number of desks' is most likely a property of contract just as 'square footage' was. If we had just underlined the word desk, we would have had to have been a bit more careful. Desks certainly are objects and therefore the concept of desk certainly could represent a class in our program. However, in this program do we care about desks in any other way other than the number of them. For example, do we care about any of the possible characteristics of desks? None of the rest of the underlined nouns refer to desks so the answer seems to be no. When we get to the italicized verbs we will also see that none of them describe behaviors of desks so nothing about desks seems to concern us or Olympia except their number in a contract. Therefore in this program desks are not objects.

Note that in other programs desks could play a significant role and need to be modeled as objects. Consider a program which acts as a tool for doing office designs. In such a program users would be creating office spaces and placing desks as well as chairs, tables, portable walls, etc. in those spaces as part of the design process. Such a program would need to keep track of the location of each desk, its style, size etc. Desks could be moved around, deleted, and otherwise modified. Now desks have properties (characteristics) and behaviors, making them very likely candidates for class status.

Back to our problem: 'number of days' fits in the same category as 'square footage' and 'number of desks'. 'Per week charge' is also a property of the contract although we will see later that there is some discussion about how to represent it. That leaves three nouns to analyze. 'Information' is really just a collective word for the various properties we have been discussing. It was useful in the problem narrative but adds no detail here so can be ignored. ' Formula' is tricky. Yes, one can say that contracts have a formula but we will see that the formula here will be represented as a piece of code that calculates the 'per week charge'. In a programming context the word formula hints at an action ( a piece of code) more often than at a property.

Finally, we have 'office complex'. There are a number of office complexes involved in this program - one for each contract probably. Yet, as with desks, there do not seem to be any properties (or behaviors as we will see in a moment) associated with this potential class. If you think about it, we really aren't interested in office complexes in this program except that contracts are associated with them. Thus, this noun seems to be another one that was useful in the problem narrative but not useful in the program itself.

We wind up with one class (contract) and a small set of properties for that class. At this point you may be thinking, "You (the authors) wound up with that, but not me. I could never repeat this process on my own." You may well be correct. Indeed, it would be possible and quite easy to come up with a different and probably invalid set of candidate classes. The class discovery/design process is an art form and there is no clear set of criteria for determining when one has come up with the best design. That is where the team approach comes in. A team is more likely to discover all the necessary classes, their responsibilities, and their collaborators.

Object oriented analysis and design is not easy. There is no simple algorithm for it. (If so, we could probably turn the process over to a computer and forget about it.) The "underline the noun and circle the verb" approach used here is only to get you started. You will need to use all your common sense and analytic skills to arrive at a good design.

This process is so hard that, in practice, it is usually done in teams - even by experts. The CRC approach, for example, is specifically meant to be executed in teams. Well managed teams allow the best ideas of each individual to come through while filtering out the mistakes. Students often resist working in teams for any number of reasons (grades, schedules....) but if you want to be trained for the real world, you need to get used to team work.

What you also need to do at this point is study the designs of others who are more experienced and then practice. That is what much of this course is about. Many of the design exercises in this course will expect you to work in teams. Try to learn from your fellow students. In addition, you may well be asked to turn in your designs before you proceed to the coding stage. This will help ensure that what you code is based on good design.

VI. Discovering the Responsibilities of the Contract Class
If we accept the conclusion that we only need one class in our program, we know that we need only one CRC card. (The reader is again reminded that we are starting with a simple example, one that probably could be done without the use of a tool such as CRC cards, but that such a simple example will hopefully make the process clear.) What then are the responsibilities of this class? To answer this we work with the italicized verbs.

As with the nouns we first need to consider if there are any verbs that can be easily ignored. Remember that a class responsibility is a behavior that instances of the class should be capable of performing. Therefore, what we are looking for here are behaviors that contracts will be expected to perform. (Again, it might help if you think of a contract as an actor.)

Problem narratives often include verbs that refer to the person or organization for whom the program is being written. In this example, we have "Olympia owns" , "Olympia wishes", and "she has". These are not actions of a contract and can be ignored. The remaining verbs include:

keep track of    involves to be cleaned
initiate provided change
get is calculated    

It turns out that many verbs provide very strong hints about the structure and behavior of classes. As an example of structure (another way of looking at properties): whenever you see verbs such as "keep track of", "saves", "stores", "holds", whose subject noun is a candidate class (such as contract in this example), the words after those verbs are likely to be properties of the class. In the program narrative the first use of 'keeps track of' refers to the program itself and can be ignored on the same basis that 'owns', wishes' and 'has' were ignored. However, the second usage has 'contract' as its subject and refers to 'square footage', number of desks' and 'number of days'. We can interpret this to mean that the class 'contract' has the properties 'square footage', number of desks' and 'number of days'. Here is corroborative evidence that we are on the right track with our analysis.

Such verbs hint at the characteristics of a class, but they do not indicate any behavior required of the class. On the other hand, verbs such as change, set, or modify indicate both possible properties and the action of changing, setting or modifying the values of those properties. (They indicate properties because something can't be changed or modified or set unless it exists and it is not the object that changes - objects of one type (class) do not become objects of another type). Therefore, in our example, we can conclude that instances of class contract must be able capable of (responsible for) changing the 'information' they hold, that is, the 'square footage', number of desks' and 'number of days' since the narrative talks about the values of these properties changing.

At this point we have the following responsibilities for the 'contract' class:

    Change Square Footage
    Change Number of Desks
    Change Number of Days

We now move on to the verb 'initiate' which has a very specific meaning in this example and in object oriented design in general. The idea behind this word is that a contract must be created (started, initialized, instantiated, constructed) with specific values for at least some of its properties before it can be used. (For example, could an actual contract (as opposed to the idea of a contract) exist without referring to a specific office.) This idea of creating specific instances with specific attribute values is true of all objects. A class simply describes a type of object. Each specific object (instance) belonging to some class must be explicitly created. Therefore each class must have at least one function that handles instance creation (initialization). This will be true even if there is no verb such as initialize, create, construct, or instantiate in the problem narrative. Such functions are called constructors.

In our example, we are told that all the information for a contract must be provided at initialization time. This is a decision made by Olympia and need not have been the case for contracts and, as we will see, is often not the case for other classes. To pursue this for a moment, suppose that we had included the name of the person who will be signing the contract as part of the information held in the contract. (This would certainly be part of any real contract record but is ignored here for simplicity and ease of programming.) Then one could imagine an initialization function (constructor) that requires at least the name (how can you have a contact without that contract involving a specific person or organization) but that, for the moment, leaves blank some of the details of the contract.

Another verb that often provides a useful hint is 'get'. It strongly implies that the class must be capable of reporting the values of the properties. This does not mean that the values are output but that they can be retrieved from an instance and used by some other part of the program. In concrete terms what this means is that there is a function (after all, functions implement behaviors) for each property that returns the value of the property. It is usually considered wise to not have the function output the value itself because the object does not know how the value is to be used. Maybe it is not going to be output.

In our example then we need at least three such 'get' functions, one for, the 'square footage', number of desks' and 'number of days'. In this material we will use the word 'provide' instead of 'get' to avoid some confusions students sometimes have. Thus, the contract class has the following additional responsibilities:

    Provide Square Footage
    Provide Number of Desks
    Provide Number of Days

We probably also need a 'get' or 'provide' function for the 'per week charge' also but this is a special kind of property since it is a calculated property. When we look at the verb 'calculated' the question arises should there be a memory location set aside to hold the 'per week charge' or should it be calculated every time it is needed? If it is calculated every time it is requested then the program must do the math over and over even if none of the values upon which it depends have changed. Why do that work over and over? But, suppose we do set aside a memory location to hold the 'per week charge' and do the calculation once. Now, suppose that later the number of desks in the office changes. Since this is one of the properties upon which the 'per week charge' depends, any change in it forces a change in the per week charge. How do we make sure that the 'per week charge' is again calculated? It turns out that although one can write code to accomplish this, it is often easier to simply have an instance redo the calculation whenever the value is asked for. The conclusion, therefore, is that the contract class will have a 'Provide Per Week Charge' function but there will not be a 'weekly charge' property.

There are three verbs left:

involves     to be cleaned     provided

None of them really involve behaviors of contracts. 'Provided' refers to the need of the program ( and ultimately the user of the program 'to provide' information while 'to be cleaned' certainly is not a behavior of a contract. 'Involves' implies a relationship. Is it possible that we have skipped a class or classes - classes that would be responsible for providing and cleaning or that would be 'involved' with the contract class? This possibility should not be overlooked - one part of the design process can often help in some other aspect of design. However, in this case there is nothing to worry about. First, the problem narrative says that "contracts involve office complexes" but we have already carefully analyzed the noun office complex and decided that it did not represent a class that would be useful in this program. Second, the other two verbs really refer to the user of the program - the user who would be 'providing' the information and the user or an associate who would be doing the 'cleaning'. In our analysis we also decided that 'user' was not a useful class concept in our program.

To summarize: Our analysis shows that we need one class, call it contract, that has eight behaviors or responsibilities:

    Initialize
    Change Square Footage
    Change Number of Desks
    Change Number of Days
    Provide Square Footage
    Provide Number of Desks
    Provide Number of Days
    Provide (Calculate) Per Week Charge

Since there is only one class we do not need to worry about collaborators. Here then is the CRC card for the class 'contract'

VII. 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:

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. 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 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.

VIII. Some Additional Ideas on Discovering the Responsibilities of a Class
Our analysis has discovered three basic types of member functions that implement the responsibilities of a class

  • those that 'construct' or initialize an instance of a class;
  • those that provide (return) the value of a data member; and
  • those that change, modify or set the value of a data member.

Such interface functions are often not 'discovered' until one actually begins coding. That is perfectly OK - a good design is meant to give the programmer a plan to follow and plans can be modified along the way. However, it is best to determine as much as you can at design time so you should ask the following questions of each class:

  1. What kind of constructors are needed? It is VERY rare that a class does not need at least one constructor so the issue here is what information do the constructor(s) need.

  2. Are there data members whose values will be needed outside the class? If so, for each such data member a 'get' or 'provide' function is needed since the data member values are encapsulated inside the instances.

  3. Are there data members whose values will be changed, set or modified by functions that are not members of the same class? If so, for each such data member, a 'change' function is needed - since, again, the data members are encapsulated inside the instances. (See above and below for a discussion of encapsulation.)

IX. Class Declarations
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 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 actions 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 relates 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. (Remember, this was done to provide for 'controlled change' of property values.) 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 organization or structure of encapsulated data members to be hidden or 'unavailable' outside the object.

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. 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 to 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:

    class contract
    {public:
    .
    .
    private:
    .
    .
    };

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 we have determined need to be included in the class 'contract' are:

    Initialize
    Change Square Footage
    Change Number of Desks
    Change Number of Days
    Provide Square Footage
    Provide Number of Desks
    Provide Number of Days
    Provide (Calculate) Per Week Charge

B. Declaring Constructors
The first function is the constructor. Its purpose should be obvious - to instantiate (create) an instance of the class 'contract'. And, from that, the goal state is clear- a new instance of class 'contract' now exists. It is considered good design to usually not have constructors have inputs and outputs as part of the idea that each function should have one and only one purpose. Let other function(s) (usually ones that are not part of the class) handle acquiring the necessary information for the instance from the user or wherever. In this way, the constructor focuses solely on its task of creating a specific instance of a class without worrying about where the information for that instantiation came from or how valid the information is.

That takes care of the input-output design issue. Now for the receives and returns. Special rules exist for constructors in C++. First, any constructor for a class must have the same name as the class. Therefore, the constructor for the class 'contract' must be called contract. Second, constructors, by definition, do NOT return anything and should not have the word 'void' in front of them. This means we can ignore the second of the two questions usually asked of functions. The first is still relevant:

    1. What, if any, data does the called function need from the calling function to accomplish its task?

For all constructors the answer takes the same form - what is needed are the values for whatever properties need to be set at the moment the instance is created. This information is provided to us in the problem narrative. It says that the values for the properties 'square footage, 'number of desks, and 'number of days' must be provided. So, that is the data that must be passed to the constructor for the class' contract'. Later we will see slightly more complex versions of this answer, especially involving the idea of default values - values that are assumed to be valid unless the class is told otherwise. For instance, if almost all office cleaning contracts where for five days (Monday ... Friday) we could have five as a default value for 'number of days' and pass in a different value only when needed. (You do not know how to do that yet but you will learn!). In any case, determining what a constructor needs as 'receives' always comes down to determining what properties must be initialized and how.

Here is the design for the constructor:

    Contract
    Purpose: to instantiate (create) an instance of the class 'contract'.
    Goal: a new instance of class 'contract' now exists

    Inputs: NONE
    Outputs: NONE

    Receives: Square Footage, Number of Desks, Number of Days (all Integers)
    Returns: NONE

It is rare that the algorithm for a constructor is anything more complex than a series of assignment statements involving the properties being initialized so you will not be required to explicitly write an algorithm for the constructor, Be sure, however, that you understand what is supposed to happen and review carefully the definition of the constructor below.

The declaration of the constructor (inside the declaration of the class)looks like this:

    class contract
    	{public:
    	    Contract(int sqFootage, int numDesks, int numDays);
    /*
    		Purpose:	to instantiate (create) an instance of the class 'contract'. 
    		Goal:		a new instance of class 'contract' now exists
    
    		Inputs: 	NONE
    		Outputs:	NONE
    		Receives:	Square Footage, Number of Desks, Number of Days (all Integers)
    		Returns:	NONE
    */
    
    	.
    	.
    	private:
    	.
    	.
    	};
    
Again, note that no type is placed in front of the function name and that the function name is the same as the class name. The lack of a return type in front might cause a bit of confusion. Not mentioned so far in this guide is the rule that usually a function without a return type is assumed to return an integer. (Here is another example of a default.) In other words, if you declare any regular function and 'forget' to place a return type at the beginning of the function declaration, the C++ compiler will assume you mean for the function to return an integer. A constructor is the one exception to this. ( C++ knows to treat this function declaration in a special way because it notes that it has the same name as the class inside which it is being declared.)

C. Declaring Functions that Change the Values of Properties
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 the first one, 'Change Square Footage'. As so often is the case the purpose is obvious from the name, as is the goal state. Also, using the same argument we made for constructors, we will assume that there are no inputs and outputs - let this and all functions serve only one purpose. All that is left is to decide what is received and returned. So, what does this function need 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

Inputs: NONE
Outputs: NONE

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:
    	    Contract(int sqFootage, int numDesks, int numDays);
            /*
    		Purpose:	to instantiate (create) an instance of the class 'contract'. 
    		Goal:		a new instance of class 'contract' now exists
    
    		Inputs: 	NONE
    		Outputs:	NONE
    		Receives:	Square Footage, Number of Desks, Number of Days (all Integers)
    		Returns:	NONE
    		*/
    
    	   
    	   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
    		Inputs:	NONE
    		Outputs:	NONE
    		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:
    	.
    	.
    	};
    
D. Declaring Functions that Return or Provide the Values of Properties
There are four functions that provide the values of properties belonging to instances of the class ' contract'. As discussed earlier, three of these ('ProvideSquareFootage', 'ProvideNumberOfDesks', and 'ProvideNumberOfDays') simply return the value found in the relevant property. As with the 'Change....' functions the design and declarations are all the same. A quick analysis reveals the pattern:

First, the function name gives away the purpose and goal of the function. And, again, there are no inputs or outputs - based on the same arguments we used for the 'Change ...' functions. 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.

    class contract
    	{public:
    	    Contract(int sqFootage, int numDesks, int numDays);
            /*
    		Purpose:	to instantiate (create) an instance of the class 'contract'. 
    		Goal:		a new instance of class 'contract' now exists
    
    		Inputs: 	NONE
    		Outputs:	NONE
    		Receives:	Square Footage, Number of Desks, Number of Days (all Integers)
    		Returns:	NONE
    		*/
    
    	   
    	   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
    		Inputs:	NONE
    		Outputs:	NONE
    		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
    		Inputs:	NONE
    		Outputs:	NONE
    		Receives:	NONE
    		Returns	the Square Footage (an integer)
    		*/
    
    	int ProvideNumberOfDesks();
    	// Design comments go here.
    
    	int ProvideNumberOfDays();
    	// Design comments go here.
    
    	private:
    	.
    	.
    	};
    
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 so 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 quire 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

As with all functions that do not indicate any inputs or outputs in the purpose, this function does not have any inputs or outputs. We proceed then to the 'questions'. First, "what does this function need to accomplish its task?" The answer to that is found in the formula:

    Weekly Charge = Days per Week * ($0.05 per square feet + $5 per desk)

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.

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'.

    double ProvidePerWeekCharge();
    /*
    Purpose: To calculate and return the per week charge for an office
    Goal: The per week charge is returned

    Inputs: NONE
    Outputs: NONE

    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. Perhaps it is not clear that the properties of an instances 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 member data item is really a memory location with the property name being the symbolic name for that memory location. In other words, Member data elements are variables with restricted access.

We have seen how local variables have access restricted to the function they are local to. Member data are variables with access restricted to the instance they belong to. 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. It is the constructor that creates instances and we will see in a bit how to use the constructor.

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:
    		 Contract(int sqFootage, int numDesks, int numDays);
            /*
    		Purpose:	to instantiate (create) an instance of the class 'contract'.
    		Goal:		a new instance of class 'contract' now exists
    
    		Inputs: 	NONE
    		Outputs:	NONE
    		Receives:	Square Footage, Number of Desks, Number of Days (all Integers)
    		Returns:	NONE
    		*/
    
    
    		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
    		Inputs:	NONE
    		Outputs:	NONE
    		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
    		Inputs:	NONE
    		Outputs:	NONE
    		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
    		Inputs:	NONE
    		Outputs:	NONE
    		Receives:	NONE
    		Returns:	The Per Week Charge (a double)
    		*/
    	private:
    		int squareFootage;
    		int numberOfDaysPerWeek;
    		int numberOfDesks;
    };
    
A quick review of the process and patterns discussed here:
  1. Constructors must have the same name as the class itself;

  2. There usually are a number of 'Change' functions, each of which have one value they receive- the new value of the data member (property) to be changed - and no values returned;

  3. There are usually a number of 'Provide' functions, each of which receives nothing and returns one value- the value of a specific data member;

  4. All function members that will be used by the rest of the program are public while data members are usually private;

  5. It is common for the public elements of a class be declared first. However, some programmers prefer to code the data member declarations first since knowing that the names for the data members helps code the function members. One can meet both goals, of course, by putting the public part of the class first, but coding it after completing the private part.

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.

X. Defining a Class
We now proceed to define the member functions which at this point is equivalent to defining the class. Just as with the code we saw last chapter, the definitions for member functions are usually kept separate from the declarations- and basically for the same reason - a class's declaration can be included a number of times but the definition can be included only once.

With classes the separations are usually more distinct. It is common practice to put the class declaration in one file, the class definition in a second file, and the program that uses the class in a third file. If there is more than one class in a program, each class gets its own declaration file and definition file. (Some programmers combine a number of class declarations in one file but the reasons for that are usually based on issues we have not discussed here.)

All the code we have written so far in this chapter should be in one file. If you recall from our earlier work, all files should have at least two comment lines at the top providing the name of the file and a brief description of what the file is about. By convention files that declare classes use the file extension '.h' - for 'header' file. We have already used one header file in the code in earlier chapters - "iostream.h". That file was provided by the compiler distributors and we included in by using the "#include" directive followed by the file name surrounded by angle brackets, <...>. The header file for the 'contract' class will also be included via the "#include" directive but the file name will be placed in double quotes, "...", to indicate that it is a programmer-defined header file.

In the case of the code we just wrote we should have added two lines at top similar to:

// Declaration of the class Contract
// File: contract.h

Similarly, for the file to hold the class definitions, we begin with two comment lines:

// Definitions for the class Contract
// File: contract.cpp

Note that the file has the same name except that the extension is different. We use the .cpp extension because this file contains instructions for the individual functions. Since the specifications for the problem go in the file containing 'main' they do not appear here or in the '.h' file. From what we have learned already, what comes next is the line or lines to 'include' any necessary files. In our analysis above we decided to not have any of the functions provide for input or output. For that reason, we do not need to have the line:

#include <iostream.h>

in our code. However, there is one header file we do need to include. Our definitions for the class 'contract' are going to use the various elements of the 'contract' declaration and, therefore, that information needs to be available in this file. To make it available we need to include the header file for the contract class - the file we called 'contract.h' - using the code:

#include "contract.h"

You can think of the 'include' directive as telling the computer to copy in the code found in the file 'contract.h'.

You might ask then why we don't just have one file that has the declarations and the definitions as we did in our previous programs. For simple programs such as the one we are presently working on we could do exactly that. However, this would not be wise in more complex programming situations. First, we have seen the importance of breaking our work up into pieces. For programs of thousands, hundreds of thousands, even millions of lines of code, having one file for the whole program would simply be unworkable. Second, we would like to have our code be re-usable. If someone writes a piece of code that provides specific functionality, it would be smart to include that code in any program that requires that functionality without including unnecessary additional code. Indeed, such re-usability is supposed to be a key feature of the object oriented approach. Once a class is declared and defined, it should be possible to use the code for that class in any programming problem requiring that class.

Third, when we break a large program up into separate files, the code in some of those files at least may need to know about a specific class. Thus, we may need to include the declaration code for that class in those files. We would not want, however, to include the class definitions in each file because, if we did so, the final, combined program would have more than one definition for the same member functions. This is illegal.

The final answer to the question at this point might best be, "Trust the experience of professional programmers." Given that, let's proceed. We will start by defining our constructor. The function members of a class are closely associated with a class. So, closely that we need to tell the system that the function we are about to declare belongs to the class 'contract'. Here then is the first line of the definition for the constructor 'Contract':

Contract::Contract(int sqFootage, int numDesks, int numDays)

The 'Contract::' at the front of this line tells the compiler that what follows is a function definition for a member function of the class 'contract'. The fact that the function being defined has the same name as the class, indicates to the compiler that it is a constructor for the class. Without the 'contract::' the function 'contract' would not be seen by the compiler as associated with any class at all and certainly would not be seen as a constructor. It would be considered the same as the function definitions we have already seen.

The rest of the code for the constructor is very simple. As we have already discussed, the purpose of the constructor is to create an instance and assign values to all or some of an instance's data members. According to our analysis, all the properties need initialization - that is the reason we have three 'receives' in the declaration. The code now uses those receives.

Contract::Contract(int sqFootage, int numDesks, int numDays)
{

    squareFootage = sqFootage;
    numberOfDesks = numDesks;
    numberOfDaysPerWeek = numDays;
}

One mistake that beginners often make is giving the same name to a parameter (a receive) that they give to a data member. Note that in this code 'squareFootage' is the symbolic name of the memory location holding the number of square feet involved in some specific contract, while 'sqFootage' is the symbolic name for the memory location used to pass the information to the constructor. The value in 'sqFootage' will be assigned to the memory location symbolized by 'squareFootage'. If both symbolic names were the same, how would the compiler know which memory location was to have its value assigned to which other memory location?

The code for most of the constructors you will work with in this course will be as simple as this one - essentially some set of assignment statements. One other detail that should be noted - because it sometimes catches students - is that there is no semicolon at the end of the function definition.

We move on then to the other function members. It turns out that there is little new here other than the need for classname:: before each function definition. The 'change' functions all are 'one-liners', for example:

void Contract::ChangeSquareFootage(int sqFootage)
{

squareFootage = sqFootage;
}

The same is true of all but one of the 'provide' functions, for example:

    int Contract::ProvideSquareFootage()
    {
    return squareFootage;
    }

The only 'provide' function that is different is the one we have already seen as different because it does a calculation:

    double Contract::ProvidePerWeekCharge ()
    {
      double perWeekCharge; perWeekCharge = numberOfDaysPerWeek * (0.05 * squareFootage + 5 * numberOfDesks);
      return perWeekCharge;
    }

A few points about this member function definition:

  1. Since we decided to not have a separate data member for the "Per Week Charge", we need to declare a local variable here to hold its value.

  2. The variable names used in the instruction implementing the formula must match those found in the class declaration. These names represent the values in the data members for some specific instance of the 'contract' class. We will see a moment how the system knows which instance to use.

  3. When the "Per Week Charge" is returned and the function is completed, the local variable 'perWeekCharge' disappears. In other words, the link between the variable name and a specific piece of memory is severed and the value is lost. (Since we decided above not to have a data member for the "Per Week Charge", this is just what we want to happen.)

Here then is the completed class definition:


// Definitions for the class Contract
// File: contract.cpp

#include "contract.h"

Contract::Contract(int sqFootage, int numDesks, int numDays)
{	squareFootage = sqFootage;
	numberOfDesks = numDesks;
	numberOfDaysPerWeek = numDays;
}

void Contract::ChangeSquareFootage(int sqFootage)
{ 	squareFootage = sqFootage;
}

void Contract::ChangeNumDesks( int numDesks)
{	numberOfDesks = numDesks;
}

void Contract::ChangeNumDays( int numDays)
{	numberOfDaysPerWeek = numDays;
}

int Contract::ProvideSquareFootage()
{	return squareFootage;
}

int Contract::ProvideNumberOfDesks()
{	return numberOfDesks;
}

int Contract::ProvideNumberOfDays()
{	return numberOfDaysPerWeek;
}

double Contract::ProvidePerWeekCharge ()
{	double perWeekCharge;
	perWeekCharge = numberOfDaysPerWeek * (0.05 * squareFootage + 5 * numberOfDesks);
	return perWeekCharge;
}
XI. Using Class Declarations in a Program

A. Declaring the Instances
Looking back at the problem narrative for this class we recall that Olympia wants to keep track of five contracts which means she should be able to get or change the information about these five contracts. The code we have written so far provides the data structure and functionality to do this but it does not provide Olympia with any way to take advantage of what we have written. It is now time to design and write the code for a program that that will use the contract class to meet Olympia's needs.

The design process has turned up one class with a number of basic functions and properties. A program that gave Olympia all the capabilities she would like could become quite complex. Let's start, however, with a simple program that really only tests the functionality of our design. Such a program is called a driver program. It is not really useful in itself except as a test vehicle.

The best way to test the functionality of a class is to exercise (use) the various member functions. In our case then let's create five instances of 'contract' and implement the following steps:

  1. Display the property values for each instance. This will test the constructor and the 'provide' functions
  2. Change the property values for each instance.
  3. Display the property values again. These last two steps will test the 'change' functions.

Notice that key to testing like this is deciding beforehand what the results should be at each point in the program. In this case, we first need to determine what information (what values for each data member) each instance will be instantiated with. Those same values should be output as a result of step 1. We then need to determine how we are going to change each of those values and look for the new values as output. In the case of the Per Week Charges, we will need to do our own calculating in order to compare the expected outputs with the actual outputs. In many ways this is like the trace process we have already seen. The big difference, of course, is that we are comparing the expected results with the computer's results as opposed to with results created by our processing of the instructions.

We have five instances to create so we need five sets of property values. As usual, we want to use values that are easy to work with. Consider the following as initial values:

Square Footage Number of Desks Number of Days
Contract 1 1000 3 5
Contract 2 500 10 3
Contract 3 200 1 4
Contract 4 2000 20 5
Contract 5 300 2 2

While we are at it, we might as well calculate the Per Week Charge:

Contract 1 Contract 2 Contract 3 Contract 4 Contract 5
$325 $225 $60 $1000 $50

There is no need to change the values of all the data members of all the instances. The numbers in the table below represent all the changes we will make.

Square Footage Number of Desks Number of Days
Contract 1 4
Contract 2 600 5
Contract 3 300
Contract 4 25 3
Contract 5

And the new charges:

Contract 1 Contract 2 Contract 3 Contract 4 Contract 5
$350 $400 $80 $675 SAME

Ordinarily we would now view the requirements of the main program to see how the task might be decomposed - as we did in the examples in the previous chapter. To avoid a few issues we will not do that immediately. Instead, all the code will be included in 'main'. First, we start with our comments:

// Program to test the functionality of the class 'contract'
// File: ch5tst1.cpp

Note that we call what we are writing here a 'program'. The other two files we have created in this chapter were not in themselves programs. They were parts of or tools to be used by programs, including this test program.

Next come the include files. To make our testing easier there will be no inputs into this program but the program will output the values of the data members of the five instances. Therefore, we need to include iostream.h. As you might expect, the program also needs to know about the declaration for class 'contract' so 'contract.h' must also be included. Here is the code so far:

// Program to test the functionality of the class 'contract'
// File: ch5tst1.cpp

#include <iostream.h>
#include "contract.h"

void main()
{
.
.
}

Note that the built-in header file (iostream.h) uses the 'angle brackets ('<', '>') in the include statement while the header file we created (contract.h) uses double quote marks. Standard header files such as iostream.h are usually stored in a predetermined directory or set of directories. The angle brackets tell the system to look in the directory or directories set up to hold the built-in header files. The quote marks tell the system to first look in the directory that contains the file being compiled and only if the file to be included is not there, look in the predetermined directories.

Instructions such as #include are actually handled by a preprocessor - a program that looks for and handles all instructions beginning with the '#' symbol BEFORE the compiler begins its work. Thus, the #include instruction brings in the code for 'contract.h', for example, and when the compiler starts, it sees that code as if it were part of the file being compiled - the file 'ch5tst1.cpp' in this case.

Now for the code for 'main'. Our first task is to declare (and define) five instances of class contract. Earlier (section VII ) we stressed the point that a class is nothing but a complex type. In fact, when it comes to declaring the instances of a class, we use almost the same syntax as we used to declare an 'instance' of type double or int or ....

Remember that we said that when we write a line such as:

double myNumber;

we are not only declaring that a variable with the name 'myNumber' exists in the program, we are also setting aside a specific memory location for this variable - we are defining the variable. As it stands, the memory location for this variable contains garbage at this point. If we rewrite the line as :

double myNumber = 2.1;

the memory location symbolized by this variable contains the value 2.1

If we were to write the line:

Contract contract1; // This is not correct

we would be requesting that the system set aside enough memory for a contract, give that memory area the symbolic name 'contract1', and don't put anything into that memory. In other words, let the memory hold whatever garbage is left over from its last use. However, this is the act of creating, instantiating, or constructing the instance and the constructor for the class 'contract' requires values for the three properties, 'square footage', 'number of days' , and 'number of desks'.

Now you see why the constructor for a class has the same name as the class itself. The word 'Contract' (upper case 'C') in the line of code above is both a type name AND a very strange 'call' to the constructor for the class. Since it is a 'call', we need to provide the three 'receive' parameters. Here is how the line should look:

Contract contract1(1000, 3, 5);

The numbers inside the parenthesis come from the table of initial values we just finished working on. What this then tells the system to do is:

  1. Create an instance of the class 'contract' by setting aside enough memory for all the properties of this instance;

  2. Call that instance 'contract1';

  3. Pass the three values (1000, 3, 5) to the constructor so they can be used by the constructor to initialize the instance.


In all prior variable declarations (and 'contract1' is now a variable) we would picture the memory set aside as a box big enough to hold a value of whatever type was being declared. Doubles required larger boxes than integers which required larger boxes than chars. For variables that are instances of some class we need to think of memory as a big box with compartments - one compartment for each property. The compartments, of course, will be of different sizes depending on whether the properties are of type int, char, or double - or even of some class. (Because classes are essentially new types, they can be used wherever types are used and, just as we can use 'if' statements inside 'whiles' inside 'if's etc., we can use classes as type names inside class declarations.)

Back to our instantiations of contract, here is our code after declaring the five instances.

    // Program to test the functionality of the class 'contract'
    // File: ch5tst1.cpp

    #include <iostream.h>
    #include "contract.h"

    void main()
    {

      Contract contract1(1000, 3, 5);
      Contract contract2(500, 10, 3);
      Contract contract3(200, 1, 4);
      Contract contract4(2000, 20, 5);
      Contract contract5(300, 2, 2);
      .
      .
    }

Be sure you understand that five distinct memory areas have been set aside, each of which represents a separate instance of the 'contract' class with its own memory locations holding the values for the three properties. (Remember, these properties are formally called 'data members'.) To ensure that you understand this notion, here is a representation of memory containing these five instances.


B. Using the Provide.... Member Functions
OK, so now we have our five instances! How do we display their property values to test that the constructor works properly? Think of these instances as animate objects and that we are going to send each instance a series of messages asking it for the values of its properties. What are the names for the messages?

    ProvideSquareFootage
    ProvideNumberOfDesks
    ProvideNumberOfDays
    ProvidePerWeekCharge

These, of course, are the names of the member functions whose purpose is to return (provide) the specific information asked for. You might object that the Per Week Charge is not a property. It is true that it is not a data member of the instances but it is a value that we as designers of the class 'contract' have agreed to provide to any program that creates instances of the class. It is irrelevant to users of this how the information is given, just that it is given on demand. (By "users" we mean programmers who take advantage of the design and code created for this class by including the code in their programs. Since at this point you are acting as designer of all classes and all programs that use the classes, this may be hard to grasp. After all, you know all the details of all aspects of your code. Try to imagine a situation where hundreds of programmers are involved in designing dozens of classes to be used in numerous different programs. Or, imagine yourself re-using a class you designed and coded but whose inner workings you have long since forgotten.)

In any case, to use these functions we can't just write, for example:

int sqFootage;
sqFootage = ProvideSquareFootage();

as we might have done with regular functions in chapter 4. Member functions always work with specific instances of a class but in this code the computer has no way of knowing from which instance we want to get the square footage.

Suppose we want the square footage first from the instance 'contract1' - a logical choice. The code for this would be written:

sqFootage = contract1.ProvideSquareFootage();

This is a call to the function 'ProvideSquareFootage' associated with the instance 'contract1'. We can read this as "Send a message to contract1 to provide its square footage and store what contract1 returns in the memory location symbolized by the local variable 'sqFootage'."

We could then write the code:

cout << "The square footage of contract1 is: " << sqFootage << endl;

to output the square footage returned.

Similarly, to retrieve and output the per week charge for contract 1 we could write:

    double charge;
    charge = contract1.ProvidePerWeekCharge();
    cout << "The per week charge for contract 1 is: " << charge << endl;

To stress a point: note how the code for this 'provide' instruction has exactly the same form as the previous one. Only the class itself knows that these two functions do something different.

The code for all the displays looks very similar - ah ha, another pattern!

    // Program to test the functionality of the class 'contract'
    // File: ch5tst1.cpp
    
    #include 
    #include "contract.h"
    
    void main()
    {	Contract contract1(1000, 3, 5);
    	Contract contract2(500, 10, 3);
    	Contract contract3(200, 1, 4);
    	Contract contract4(2000, 20, 5);
    	Contract contract5(300, 2, 2);
    
    	int sqFootage;
    	int numDesks;
    	int numDays;
    	double charge;
    
    	sqFootage = contract1.ProvideSquareFootage();
    	cout << "The square footage of contract1 is: " << sqFootage << endl;
    	numDesks = contract1.ProvideNumberOfDesks();
    	cout << "The number of desks of contract1 is: " << numDesks << endl;
    	numDays= contract1.ProvideNumberOfDays();
    	cout << "The number of days of contract1 is: " << numDays << endl;
    	charge = contract1.ProvidePerWeekCharge();
    	cout << "The per week charge for contract 1 is: " << charge << endl;
    
    	sqFootage = contract2.ProvideSquareFootage();
    	cout << "The square footage of contract2 is: " << sqFootage << endl;
    	numDesks = contract2.ProvideNumberOfDesks();
    	cout << "The number of desks of contract2 is: " << numDesks << endl;
    	numDays= contract2.ProvideNumberOfDays();
    	cout << "The number of days of contract2 is: " << numDays << endl;
    	charge = contract2.ProvidePerWeekCharge();
    	cout << "The per week charge for contract 2 is: " << charge << endl;
    
    
    	sqFootage = contract3.ProvideSquareFootage();
    	cout << "The square footage of contract3 is: " << sqFootage << endl;
    	numDesks = contract3.ProvideNumberOfDesks();
    	cout << "The number of desks of contract3 is: " << numDesks << endl;
    	numDays= contract3.ProvideNumberOfDays();
    	cout << "The number of days of contract3 is: " << numDays << endl;
    	charge = contract3.ProvidePerWeekCharge();
    	cout << "The per week charge for contract 3 is: " << charge << endl;
    
    
    	sqFootage = contract4.ProvideSquareFootage();
    	cout << "The square footage of contract4 is: " << sqFootage << endl;
    	numDesks = contract4.ProvideNumberOfDesks();
    	cout << "The number of desks of contract4 is: " << numDesks << endl;
    	numDays= contract4.ProvideNumberOfDays();
    	cout << "The number of days of contract4 is: " << numDays << endl;
    	charge = contract4.ProvidePerWeekCharge();
    	cout << "The per week charge for contract 4 is: " << charge << endl;
    
    
    	sqFootage = contract5.ProvideSquareFootage();
    	cout << "The square footage of contract5 is: " << sqFootage << endl;
    	numDesks = contract5.ProvideNumberOfDesks();
    	cout << "The number of desks of contract5 is: " << numDesks << endl;
    	numDays= contract5.ProvideNumberOfDays();
    	cout << "The number of days of contract5 is: " << numDays << endl;
    	charge = contract5.ProvidePerWeekCharge();
    	cout << "The per week charge for contract 5 is: " << charge << endl;
    }
    
Quite repetitive isn't it. Note that we can use the same variables over and over because once we have output the value of a variable the value is not useful any more in this case. Note also how the data members of each class are encapsulated. We cannot write code such as:

cout << squareFootage;

hoping to get access to the data member of some instance without making any function calls. This fails for two reasons. First, each instance has its own memory location called 'squareFootage' so how does the computer know which instance we want? Second, the data members are encapsulated so, even if there was only one instance, we would not be able to get direct access to its data members. Could we fix the problem by writing:

cout << contract1.squareFootage;

as we did with function members to indicate which instance we are interested in? Note that this does indicate which instance we are interested in BUT it has no effect on the encapsulation issue. The only way to get access to those parts of a class that are declared to be private is through the appropriate member functions. This is a bit like walking into a post office to pick up a package. You may know that a package is somewhere 'behind the counter' but you do not have direct access to it. You must ask a clerk to go back and get it for you.

C. Using the Change.... Member Functions
The next step we decided to take in our test program was to change the property values of some instances. Here is the table again of changes. (Remember, blanks in the table refer to properties that will not be changed):

Square Footage Number of Desks Number of Days
Contract 1 4
Contract 2 600 5
Contract 3 300
Contract 4 25 3
Contract 5

There are six changes that need to be made so we have six function calls to write. The form of the calls is the same as in the previous set of instructions except that this time the functions have one parameter and return nothing. To change the 'number of desks' value in contract 1 we write:

contract1. ChangeNumDesks(4);

The calls to the others look the same:

contract2. ChangeSquareFootage(600);
contract2. ChangeNumDays(5);
contract3. ChangeSquareFootage(300);
contract4. ChangeNumDesks(25);
contract4. ChangeNumDays(3);

Note that contracts 2 and 4 each have two function calls (are sent two messages) because two property values are changed. Note also that we cannot directly change the per week charge for any instance. Since there is no data member for this 'property', no member function is included to directly change its value. However, any change to any of the other three properties will result in a change in the per week charge when it is re-calculated.

To make sure that these changes work we next output the four values of the properties of the five instances. This is simply a repeat of the code you saw above. You are urged to complete this yourself. Include the necessary code to output the property values of instance five even though no changes were made to it in order to make sure your code does not accidentally make changes it should not make. You should also run this program and see if the outputs generated are what you expect - see "Putting it All Together" below. When you are finished, check out the authors' version of this code under the file names, "common.h", "contract.h", "contract.cpp", and "ch5tst1.cpp"

C. Putting It All Together
Prior to this, we used functional decomposition to break up the code. Still we fit it into one file with the exception of some built-in header files. Now we have split up the code into two separate '.cpp' files and one header files. To allow our program to function as a single unit, we need to bring these files back together.

We have already seen how to accomplish this with the header file via the include statement. As we said, the "#include …" statement copies the code in the included file into to file with the "#include" statement. This is standard in all versions of C++. However, bring together or linking the .cpp files is not so standard across C++ packages. First, we need to understand a bit about the C/C++ heritage and approach to programming. It has always been considered very important in the 'C' world that one be allowed to compile parts of a program separate from other parts. Such separate compilation is especially important in large programs where to compile a full program could take a hour or more. No one wanted to wait that long to fix one small change to one small part of a program.

Separate compilation avoids this. Each program file containing definitions can be compiled into machine code by itself with references to code found in other files finalized later through the linker. In C++ the header files with their declarations act as promises - the definition for the functions declared here is available; just wait until link time.

Separate compilation does, however, make the whole process more complex. Because one file may or may not depend on other files, it is not always clear to the compiler what should be re-compiled. The traditional approach involved what are called 'make' files in which the programmer explicitly stated what files depended on what other files. Using this information the compiler would re-compile a given file only if that file itself had been changed (based on the date and time of the file on disk) or if one of the files it depended on had been changed.

More recently, easier to use, more automated approaches have surfaced. Using a windows-mouse interface (often called a Graphical User Interface or GUI), the programmer defines the dependencies with very little typing. The dependencies may be displayed via a simple indenting scheme, making it easy for the programmer to visualize the dependencies. Such tools also allow the programmer to compile the same code for 'DOS' or 'Windows' and for 16 or 32 bit systems. You are urged to check the manual for your programming environment to determine the easiest way to perform separate compilation, etc. on your own system. (Borland C++, for example, allows programmers to use what are called projects. The general instructions for working with projects can be found in the document "BorlandProject.htm" ) Click here for the completed driver program. Follow the instructions for your version of C++ to compile and link this program with the .cpp file containing the definitions for the class member functions.