So far, our discussion about object-oriented programming has been somewhat theoretical. We have discussed the difference between classes and objects and how objects need to be
By merely adding plenty of methods and statements to the
SimpleCalculator
class of Listing 4.1, it would be possible, albeit extremely cumbersome, to write a valid and full-blown sophisticated spreadsheet application without ever being
Conversely and surprisingly, it would also be possible to write an object-oriented program in C or Pascal, but it would be
To make our previous object-oriented discussions more practical and to avoid the risk of constructing a non-object-oriented
Listing 5.1 contains the source code for a simple elevator simulation program. Its goal is to
Many abstractions were made during the design stage of the source code in Listing 5.1, allowing us to make the program simple and be able to concentrate on the essential object-oriented
The following is a brief description of the overall configuration of the elevator's system used for this simulation, highlighting the major differences to a real elevator system.
The Building class has one Elevator object called elevatorA .
One Person object residing inside the passenger variable is "using" elevatorA .
The
Elevator
object can "travel" to any "floor" that is within the range specified by the
int
type (-2147483648 to 2147483647). However, a
Person
object is programmed to
The elevator will instantly "travel" to the "destination floor."
The "movements" of elevatorA will be displayed on the console.
After the passenger has "entered" elevatorA , "he" or "she" will stay in elevatorA throughout the simulation and simply choose a new floor whenever a previous request has been satisfied.
Only the
Elevator
,
Person
, and
Building
classes are used in this simulation, leaving out
Floor
,
StatisticsReporter
, and other classes
At the end of each simulation, the total number of floors traveled by elevatorA will be displayed on the console. This number could be a very important statistic in a serious elevator simulation.
So, despite the simplicity and high abstraction of this simulation, we are actually able to extract one important statistic from it and, without too many additions, you would be able to create a small but useful simulation enabling the
Note
|
Recall that the order in which the methods of a class are written in the source code is independent of how the program is executed. The same is true for the order of the classes in your program. You can choose any order that
|
Typical output from Listing 5.1 is shown following the listing. Because the requested floor
01: // A simple elevator simulation
02:
03: using System;
04:
05: class Elevator
06: {
07: private int currentFloor = 1;
08: private int requestedFloor = 0;
09: private int totalFloorsTraveled = 0;
10: private Person passenger;
11:
12: public void LoadPassenger()
13: {
14: passenger = new Person();
15: }
16:
17: public void InitiateNewFloorRequest()
18: {
19: requestedFloor = passenger.NewFloorRequest();
20: Console.WriteLine("Departing floor: " + currentFloor
21: + " Traveling to floor: " + requestedFloor);
22: totalFloorsTraveled = totalFloorsTraveled +
23: Math.Abs(currentFloor - requestedFloor);
24: currentFloor = requestedFloor;
25: }
26:
27: public void ReportStatistic()
28: {
29: Console.WriteLine("Total floors traveled: " + totalFloorsTraveled);
30: }
31: }
32:
33: class Person
34: {
35: private System.Random randomNumberGenerator;
36:
37: public Person()
38: {
39: randomNumberGenerator = new System.Random();
40: }
41:
42: public int NewFloorRequest()
43: {
44: // Return randomly generated number
45: return randomNumberGenerator.Next(1,30);
46: }
47: }
48:
49: class Building
50: {
51: private static Elevator elevatorA;
52:
53: public static void Main
()
54: {
55: elevatorA
=
new Elevator
();
56: elevatorA.LoadPassenger
();
57: elevatorA.InitiateNewFloorRequest
();
58: elevatorA.InitiateNewFloorRequest
();
59: elevatorA.InitiateNewFloorRequest
();
60: elevatorA.InitiateNewFloorRequest
();
61: elevatorA.InitiateNewFloorRequest
();
62: elevatorA.ReportStatistic
();
63: }
64: }
Departing floor: 1 Traveling to floor: 2
Departing floor: 2 Traveling to floor: 24
Departing floor: 24 Traveling to floor: 15
Departing floor: 15 Traveling to floor: 10
Departing floor: 10 Traveling to floor: 21
Total floors traveled: 48
Before we continue with the more detailed analysis of the program, look at Figure 5.1. It connects the illustration used in Figure 3.1, shown in Chapter 3, with the concrete C# program of Listing 5.1.
The
Elevator
and
Person
classes of Listing 5.1 define abstracted versions of our now familiar counterparts from the real world. They are graphically depicted next to their C# counterparts in Figure 5.1. Each part of the
Elevator
and
Person
classes written in the C# source code (indicated by braces) has been linked to its graphical
The Building class has one Elevator , which is represented by its elevatorA instance variable declared in line 51. It also holds the Main method where the application commences. This class is never instantiated in the program. It is used by the .NET runtime to access Main and start the program.
Just as in the previous listings, I will provide a brief explanation of the lines in the source code. Many lines have not been shown because they have already been explained in a previous example.
05: Begin the definition of a class named Elevator 07: Declare an instance variable called currentFloor to be of type int; set its accesslevel to private and its initial value to 1. 10: Declare passenger to be a variable, which can hold an object of class Person. The
class Person is said to play the role of passenger in its association with the Elevator
class. 12: Begin the definition of a method called LoadPassenger. Declare it public to be part
of the interface of the Elevator class. 14: Instantiate (create) a new object of class Person by using the keyword new. Assign
this object to the passenger variable. 17: Begin the definition of a method called InitiateNewFloorRequest. Declare it public to
be part of the interface of the Elevator class. 19: Call the NewFloorRequest method of the passenger object; assign the number returned
by this method to the requestedFloor variable. 20-21: Print information about the "movement" of the operator on the command console. 22-23: Calculate the number of floors traveled by the elevator on one particular trip
(line 23). Add this result to the total number of floors already traveled by the
elevator. 24: Let the Elevator "travel" to the requested floor by assigning the value of
requestedFloor to currentFloor. 29: Whenever the ReportStatistics method is called, print out the totalFloorsTraveled
variable. 33: Begin the definition of a class named Person. 35: Declare randomNumberGenerator to be a variable, which can hold an object of class
System.Random. 37: Begin the definition of a special method called a constructor, which will be invoked
automatically whenever a new object of class Person is created. 39: Create a new object of class System.Random by using the C# keyword: new; assign this
object to the randomNumberGenerator variable. 42: Begin the definition of a method called NewFloorRequest. public declares it to be
part of the interface of the Person class. int specifies it to return a value of type
int. 43: The Person decides on which floor "he" or "she" wishes to travel to by returning a
randomly created number between 1 and 30. 51: The Building class declares an instance variable of type Elevator called elevatorA.
The Building class is said to have a "has-a" (or a composition) relationship with the
Elevator class. 53: Begin the definition of the Main method where the program will commence. 55: Instantiate an object of class Elevator with the keyword new; assign this object to
the elevatorA variable. 56: Invoke the LoadPassenger method of the elevatorA object. 57-61: Invoke the InitiateNewFloorRequest method of the elevatorA object 5 times. 62: Invoke the ReportStatistic method of the elevatorA object.
The following sections discuss important subjects related to Listing 5.1.
You are already familiar with how a class is defined. I still highlight line 5 because this is the first time you define your own class that is being instantiated and therefore used to create an object in your program.
05: class Elevator
The new feature presented in line 7 is the combination of a declaration statement and an assignment statement. This is called an initialization. private int currentFloor; is a straightforward declaration and, by adding = 1 at the end, you effectively assign 1 to currentFloor during or immediately after the creation of the object to which this instance variable belongs.
07: private int currentFloor = 1;
A variable often needs an initial value before it can be utilized in the
When a variable is assigned a value before it takes part in any computations, we say it is being
Assign the variable an initial value in its declaration (as in line 7).
Utilize a C# feature called a constructor. The source code contained in a constructor is executed when an object of a class is being created, which is the ideal time for any initializations. Constructors are informally presented shortly.
Instance variables that are not explicitly initialized in the source code are automatically assigned a default value by the C# compiler. For example, had we not assigned the value 1 to currentFloor in line 7, C# would have given it the default value 0.
Initialize All Your Variables Explicitly
|
Do not rely on the C# compiler to initialize your variables. Explicit initializations make the code clearer and avoid the source code relying on compiler initializations and their default values, which can change in the future and introduce errors into your source code. |
Tip
|
It is possible to declare a class member without explicitly
Nevertheless, use the
private
keyword to declare all
private
class
|
Line 10 states that the instance variable passenger can hold an object created from the Person class.
10: private Person passenger;
We haven't yet put a particular
Person
object here; we would need an assignment statement to do this, which you will see a bit further in this section. So far, we are merely
Now, jump down to lines 33-47 for a moment. By defining the Person class in these lines, you have effectively created a new custom-made type. So, instead of merely being able to declare a variable to be of type int or type string , you can also declare it to be of type Person , which is exactly what you do in line 10.
Note
|
int and string are built-in, pre-defined types. The classes you write and define in your source code are custom-made types. |
Notice that line 10 is closely linked with lines 14, 19, and 33-47. Line 14
Tip
|
The sequence of class member declarations and definitions is arbitrary. However, try to divide the class members into sections containing members with similar access modifiers to improve clarity. The following is one example of a commonly used style:
class ClassName
{
declarations of private instance variables
private method definitions
public method definitions
}
|
Lines 7-10 contain the list of instance variables I found relevant to describe an Elevator object for this simulation.
07: private int currentFloor = 1; 08: private int requestedFloor = 0; 09: private int totalFloorsTraveled = 0; 10: private Person passenger;
The state of any Elevator object is described by the instance variables declared in the following lines:
Line 7 — The currentFloor variable keeps track of the floor on which an Elevator object is situated.
Line 8 — A new floor request will be stored in the requestedFloor variable. The Elevator object will attempt to fulfill this request as swiftly as possible (line 24), depending on the speed of your computer's processor.
Line 9 — When the Elevator object is created, you can regard it to be "brand new." It has never moved up or down, so totalFloorsTraveled initially must contain the value 0. This is achieved by initializing it to the value 0.
The amount of floors traveled is added to totalFloorsTraveled (lines 22-23), just before a trip is finished (line 24).
Line 10
— The passenger(s) of the elevator are ultimately the decision
Abstraction and the Choice of Instance Variables
|
Recall the discussion about abstraction in the beginning of Chapter 3. The goal associated with abstraction is to identify the essential characteristics of a class of objects that are relevant to our computer program. During the process of deciding which instance variables to include in a class definition, and the declaration of them in the source code, the programmer is applying the conceptual idea of abstraction in a very practical, hands-on manner.
For example, I could have attempted to include an instance variable such as
But where can I make any use of it? I could perhaps assign the string red to it and write a method that would print the following: My color is: red
on the command console whenever called; but the exercise would be irrelevant to what we are trying to achieve in our simple little simulation so it is
Another programmer might have included an instance variable of the Elevator class that measures the number of trips performed by the elevator; the programmer might call it totalTrips and declare it as follows: The Elevator class could then be designed so that a method would add 1 to totalTrips every time a request had been fulfilled. This instance variable would enable us to keep track of another, perhaps important, statistic and so it has the potential of being useful. As you can see, there are many different ways to represent a real world object when deciding which instance variables to include. The choice of instance variables depends on the individual programmer and what he or she wants to do with each object. |
An Elevator object must be able to load a new Person object. This is accomplished by the LoadPassenger method residing inside an Elevator object (see line 12). LoadPassenger() is accessed from outside its object, and so it must be declared public . The call to LoadPassenger is made in line 56 of Listing 5.1.
12: public void LoadPassenger()
Talking About Classes and Objects
|
Consider the following declaration statement: private Person passenger;
You will come across many different, more or less correct, ways to describe what this
" passenger is a variable declared to hold an object of class Person ." Due to this lengthy description, I often use the following expression: " passenger is a variable declared to hold a Person object." In Chapter 6, "Types Part I: The Simple Types," you will see why the following description is probably the most correct but also the longest: " passenger is a variable declared to hold a reference to an object of class Person ." |
Line 14 goes hand-in-hand with line 10.
14: passenger = new Person();
new , as applied in this line, is a keyword in C# used to instantiate a new object. It will create a new instance (object) of the Person class. This new object of class Person is then assigned to the passenger variable. passenger now contains a Person object that can be called and used to perform actions.
Note
|
When an object is instantiated, an initialization of this object's instance variables is often needed. A
constructor
is a special kind of method that
To specify that a method is a constructor, it must have the same
|
By calling the
InitiateNewFloorRequest
method, the
Elevator
object is
Get a new request from the passenger (line 19)
Print out its
Update the totalFloorsTraveled statistic (lines 21-22)
Fulfill the request of the passenger (line 24).
17: public void InitiateNewFloorRequest()
18: {
19: requestedFloor = passenger.NewFloorRequest();
20: Console.WriteLine("Departing floor: " + currentFloor
21: + " Traveling to floor: " + requestedFloor);
22: totalFloorsTraveled = totalFloorsTraveled +
23: Math.Abs(currentFloor - requestedFloor);
24: currentFloor = requestedFloor;
25: }
Let's begin with line 19:
Here, we are using the following syntax introduced earlier:
where the dot operator ( . ) is used to refer to a method residing inside an object. You have already used the dot operator many times. For example you used the dot operator when you called the WriteLine method of the System.Console with System.Console.WriteLine("Bye Bye!") . This time, however, instead of calling a prewritten method of the .NET Framework you are calling your own custom-made method from the Person class.
Note
|
You can see the fixed class definition in the source code. An object, on the other hand, is dynamic and comes to life during execution of the program. |
Instead of calling a method residing inside the same object, you are calling a method residing in another object. Here, the
Elevator
object is said to send a message to the
Person
object. A call to a method residing in another object is similar to calling a local method. In Figure 5.2, I have linked Figure 3.1 with the general mechanism
After the newly received request from its passenger in line 19, the Elevator object has a current position held by the currentFloor variable and a different floor held in requestedFloor to which it must travel. If the Elevator is "brand new" and has just been created, currentFloor will have a value of 1. If the Elevator has already been on one or more "rides," currentFloor will be equal to the destination of the latest "ride." Line 20 will print out the value of the currentFloor by using the now familiar WriteLine method. The newly received passenger request is then printed in line 21.
Note that lines 22 and 23 represent just one statement; you can verify this by locating the single semicolon found at the end of line 23. The statement has been spread over two lines due to lack of space. Line 23 has been indented relative to line 22; this indicates to the reader that we are dealing with only one statement.
Absolute Value
|
The absolute value of a positive number is the number itself. The absolute value of a negative number is the number with the negative sign removed. For example, The absolute value of (-12) is 12. The absolute value of 12 is 12.
In Mathematics, absolute value is indicated with two vertical lines,
-12 = 12 |
The use of Math.Abs()
|
The Abs method of the Math class residing inside the .NET Framework returns the absolute value of the argument sent to it. Consequently, the method call Math.Abs(99) returns 99 , whereas Math.Abs(-34) returns 34. When calculating the distance traveled by the elevator, we are interested in the positive number of floors traveled, irrespective of whether the elevator is moving up or down. If we calculate the number of floors traveled using the following expression: currentFloor - requestedFloor we will end up with a negative number of floors traveled whenever requestedFloor is larger than currentFloor . This negative number is then added to totalFloorsTraveled ; so the number of floors traveled in this case has mistakenly been deducted rather than added from totalFloorsTraveled . We can avoid this problem by adding the absolute value of ( currentFloor - requestedFloor ), as has been done in line 23: Math.Abs(currentFloor - requestedFloor); |
The statement can be broken down into the following sub-instructions:
Math.Abs(currentFloor - requestedFloor) — Calculate the number of floors traveled on the next elevator ride.
totalFloorsTraveled + Math.Abs(currentFloor - requestedFloor) — Add the number of floors traveled on the next elevator ride to the current value of totalFloorsTraveled .
Assign the result of 2 to totalFloorsTraveled .
Having totalFloorsTraveled on both sides of the assignment operator might seem odd at first. However, this is a very useful operation adding the number of floors traveled on the latest elevator ride to the original value of totalFloorsTraveled . totalFloorsTraveled is the "odometer" of the elevator, constantly keeping track of the total amount of floors it has traveled. More details about this type of statement will be provided in Chapter 6.
Surprisingly, the simple assignment statement in line 24 represents the "elevator ride". It satisfies the request from passenger . By assigning the value of the requestedFloor variable to currentFloor , we can say that the elevator has "moved" from the currentFloor value it contained prior to this assignment to the requestedFloor .
Line 33 begins the definition of the second custom written class, which will be used as a template to create an object.
33: class Person
As we have seen, a Person object is created and kept inside an Elevator object from which its NewFloorRequest method is called, telling the Elevator object where its passenger wants to go next.
In Line 35, an object of the
Person
class has been enabled to make floor requests by
35: private System.Random randomNumberGenerator;
Because randomNumberGenerator is an instance variable of the Person class, it is declared private . Note that after this declaration, the randomNumberGenerator is still empty; no object has yet been assigned to this variable. We must assign an object to the randomNumberGenerator before any random numbers can be generated by this variable; lines 37-40 fulfill this important task by containing a constructor for the Person class.
The constructor concept has already been discussed and I won't drill further into this subject now. It is not important to fully understand the constructor mechanism now, as long as you are aware that the essential part is line 39, where a new object of class System.Random is assigned to the randomNumberGenerator of any newly created object of class Person .
37: public Person()
38: {
39: randomNumberGenerator = new System.Random();
40: }
The NewFloorRequest defined in lines 42-46 will, when called, generate and return a random number that indicates passenger 's next floor request. Line 45 finds a random number between 1 and 30 (specified in the parentheses .Next(1,30) ). The return keyword sends this random number back to the caller which, in this case, is the InitiateNewFloorRequest method of the Elevator object. The details of the System.Random class will not be discussed any further here. If you want to investigate this class further, please use the .NET Framework Documentation.
42: public int NewFloorRequest()
43: {
44: // Return randomly generated number
45: return randomNumberGenerator.Next(1,30);
46: }
Note
|
The programmer who implements the Elevator class makes use of the NewFloorRequest method of a Person object. However, he or she should not need to look at the method definition to use the method. He or she would most likely not be interested in how a Person object decides on the values returned and would happily be unaware of random number generators and the like. The method header and a short description of the intent of the method is all that he or she needs to know. Information of how the intent is accomplished has no place here. The method header forms a contract with the user of the method. It gives the name of the method and how many arguments should be sent along with the method call. The method then promises to return either no value (if stated void) or one value of a specific type.
So as long as we don't tinker with the method header (don't change the name of the method, the number and type of its formal parameters, or its return type), we can create all sorts of
|
In lines 53-63, we find our old friend the unavoidable Main method.
53: public static void Main () 54: { 55: elevatorA = new Elevator (); 56: elevatorA.LoadPassenger (); 57: elevatorA.InitiateNewFloorRequest (); 58: elevatorA.InitiateNewFloorRequest (); 59: elevatorA.InitiateNewFloorRequest (); 60: elevatorA.InitiateNewFloorRequest (); 61: elevatorA.InitiateNewFloorRequest (); 62: elevatorA.ReportStatistic (); 63: }
Even if Main() is positioned at the end of the program in this case, it contains, as always, the statements that are to be executed first when the program is started. You can view Main() to be a "control tower" of the whole program. It uses the overall functionality provided by the other classes in the program to direct the general flow of execution.
Briefly, the Main method initially creates an Elevator object and assigns it to elevatorA (line 55); instructs it to load in a passenger (line 56); asks elevatorA to request a "next floor request" along with its fulfillment five times (lines 57-61); and finally, it asks the Elevator object to report the statistic it has collected over those five trips (line 62).
Note how relatively easy it is to conduct this simulation from the Main method. All the hard detailed work is performed in the Elevator and Person classes.
The three user-defined classes, along with the
System.Random
class of Listing 5.1, collaborate to provide the functionality of our simple elevator simulation program. The
Building
class contains an
Elevator
object and calls its methods, the
Elevator
object employs a
Person
object to direct its movements, and the
Person
object uses a
System.Random
object to decide the next floor. In well-
If two classes are to collaborate, they must have a relationship (interact) with each other. Several different relationships can exist between two classes, and it is up to the designer of the program to decide which particular relationships should be implemented for the program at hand. This design phase typically takes place when the classes of the program have been identified (stage 2b of the design process described in Chapter 2, "Your First C# Program"). The following section discusses a few commonly found relationships.
Building - Elevator and Elevator - Person form two kinds of relationships that we will use as examples.
A typical building is
51: private static Elevator elevatorA;
This allows
Building
to hold an
Elevator
object and call its
public
methods. A class can have many different instance variables holding many objects of different classes. For example, we could also have equipped
Building
with instance variables representing a number of
Floor
objects. This concept of constructing a class (
Building
) with other classes (
Elevator
or
Floors
or both and many others) is
If the aggregation relationship (as in the case of the
Building
-
Elevator
relationship), is reflecting a situation where one class is an integral part of the other, we can further call this aggregation a
composition.
(In a moment you will see why the Elevator-Person relationship is an aggregation but not a composition). The composition relationship can be illustrated with a Unified Modeling Language (UML) class diagram as shown in Figure 5.3. The two rectangular boxes symbolize classes and the line connecting them with the black diamond (pointing at the whole class) illustrates a composition relationship between the classes. Both classes are marked with the number 1 to
The Unified Modeling Language (UML): The Lingua Franca of OO Modeling
|
Pseudocode is a useful aid in expressing algorithms that are implemented in single methods because they are read from top to bottom, like the runtime executes the program, and because it abstracts away from the hard-coded rigor of a computer language (semicolons, parentheses, and so on). However, classes can consist of many methods, and larger programs consist of many classes. Pseudocode is not a suitable tool to illustrate models of how the classes of a program relate, because classes break free of the sequential procedural-oriented way of thinking (every class can
To model class relationships and the overall architecture of an OO program effectively, we need a language that allows us to abstract away from the internal details of methods and, instead, provide us with the means to express class relationships and OO concepts on a suitable level of detail. For this purpose, most OO programmers today, irrespective of their programming language, use a graphical diagramming language called the Unified Modeling Language (UML). UML is a feature-rich language and requires a whole book to be amply presented;
You can get detailed information about UML from the non-profit organization Object Management Group (OMG) (www.omg.org) at www.omg.org/uml. Many good UML books have been written including The Unified Modeling Language User Guide by the originators of UML, Grady Booch, James Rumbaugh, and Ivar Jacobson. |
A button is an integral part of an elevator, but a passenger is not. (The elevator is still fully operational without a passenger, but not without its
The whole UML class diagram for the program in Listing 5.1 is shown in Figure 5.5. UML allows us, as shown, to divide the rectangle representing a class into three compartments—the upper compartment contains the name of the class, the middle compartment the instance variables (or attributes), and the lower
Permanent relationships between classes, such as the aggregation and composition relationships discussed in the previous sections, are generally called
structural relationships
or, more
Other example of associations that are not aggregations are
Employee works for Company
BankCustomer
Note
|
Associations that precisely connect two classes are called binary associations; they are the most common kind of association. |