Object-Oriented Programming: A Practical Example

   


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 instantiated before they can actually perform any actions. You have seen a couple of simple classes in Listings 3.1 of Chapter 3 and 4.1 of Chapter 4, but they didn't contain any real objects; they were passive containers only created to hold the Main method. Actually, none of these programs can be regarded as being particularly object-oriented. Because they only have methods containing sequences of statements one executed after the other, they resemble programs written in the procedural programming style.

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 concerned about writing any other classes, learning any theoretical object-oriented principles, or ever using any mentionable object-oriented C# features. The structure of this program would be closer to a program written in a procedural language like C or Pascal than it would to a typical object-oriented C# program.

Conversely and surprisingly, it would also be possible to write an object-oriented program in C or Pascal, but it would be awkward because these languages, as opposed to C#, do not have any built-in support for this paradigm.

To make our previous object-oriented discussions more practical and to avoid the risk of constructing a non-object-oriented full-size spreadsheet program, I have provided an example illustrating a couple important C# features closely related to our theoretical discussion about classes, objects, and instantiation.

Presenting SimpleElevatorSimulation.cs

Listing 5.1 contains the source code for a simple elevator simulation program. Its goal is to illustrate what a custom-made object looks like in C# and how it is instantiated from a custom written class. In particular, it shows how an Elevator object is created and how it calls a method of the Person object named NewFloorRequest and triggers the latter to return the number of the requested "floor," enabling the Elevator to fulfill this request.

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 parts of the source code.

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 randomly choose floors between 1 and 30.

  • 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 considered important for a full-blown simulation.

  • 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 user to gain valuable insights into a real elevator system.

Please take a moment to examine the source code in Listing 5.1. Try first to establish a bigger picture when looking through the code. For example, notice the three class definitions (Elevator, Person and Building) that constitute the entire program (apart from lines 1 4). Then notice the methods defined in each of these three classes and the instance variables of each class.

Note

graphics/common.gif

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 suits your style. In Listing 5.1, I chose to put the class containing the Main method last, and yet Main is the first method to be executed.


Typical output from Listing 5.1 is shown following the listing. Because the requested floor numbers are randomly generated, the "Departing floor" and "Traveling to" floors (except for the first departing floor, which will always be 1) and the "Total floors traveled" will be different on each run of the program.

Listing 5.1 Source Code for SimpleElevatorSimulation.cs
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 

Overall Structure of the Program

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.

Figure 5.1. Linking Figure 3.1 with actual C# program.
graphics/05fig01.gif

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 counterpart with arrows. Notice how the public methods of the two classes (the interface) encapsulate the hidden private instance variables. In this case, no private methods were needed.

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.

Listing 5.2 Brief Analysis of Listing 5.1
05: Begin the definition of a class named Elevator 07: Declare an instance variable called currentFloor to be of type int; set its access  graphics/ccc.giflevel to private and its initial value to 1. 10: Declare passenger to be a variable, which can hold an object of class Person. The  graphics/ccc.gifclass Person is said to play the role of passenger in its association with the Elevator  graphics/ccc.gifclass. 12: Begin the definition of a method called LoadPassenger. Declare it public to be part  graphics/ccc.gifof the interface of the Elevator class. 14: Instantiate (create) a new object of class Person by using the keyword new. Assign  graphics/ccc.gifthis object to the passenger variable. 17: Begin the definition of a method called InitiateNewFloorRequest. Declare it public to  graphics/ccc.gifbe part of the interface of the Elevator class. 19: Call the NewFloorRequest method of the passenger object; assign the number returned  graphics/ccc.gifby 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  graphics/ccc.gif(line 23). Add this result to the total number of floors already traveled by the  graphics/ccc.gifelevator. 24: Let the Elevator "travel" to the requested floor by assigning the value of  graphics/ccc.gifrequestedFloor to currentFloor. 29: Whenever the ReportStatistics method is called, print out the totalFloorsTraveled  graphics/ccc.gifvariable. 33: Begin the definition of a class named Person. 35: Declare randomNumberGenerator to be a variable, which can hold an object of class  graphics/ccc.gifSystem.Random. 37: Begin the definition of a special method called a constructor, which will be invoked  graphics/ccc.gifautomatically 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  graphics/ccc.gifobject to the randomNumberGenerator variable. 42: Begin the definition of a method called NewFloorRequest. public declares it to be  graphics/ccc.gifpart of the interface of the Person class. int specifies it to return a value of type  graphics/ccc.gifint. 43: The Person decides on which floor "he" or "she" wishes to travel to by returning a  graphics/ccc.gifrandomly created number between 1 and 30. 51: The Building class declares an instance variable of type Elevator called elevatorA.  graphics/ccc.gifThe Building class is said to have a "has-a" (or a composition) relationship with the  graphics/ccc.gifElevator 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  graphics/ccc.gifthe 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.  

A Deeper Analysis of SimpleElevatorSimulation.cs

The following sections discuss important subjects related to Listing 5.1.

Defining a Class to Be Instantiated in Our Own Program

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 
Initializing Variables

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 computations in which it takes part. currentFloor in line 7 is a good example. We want the elevator to start at floor number 1 so we initialize it to this value.

When a variable is assigned a value before it takes part in any computations, we say it is being initialized. Two important methods exist to initialize an instance variable. You can

  • 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

graphics/bulb.gif

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

graphics/bulb.gif

It is possible to declare a class member without explicitly stating its accessibility by leaving out the public or private keyword. The accessibility is then, by default, private.

Nevertheless, use the private keyword to declare all private class members to enhance clarity.


Declaring a Variable Representing an Object of a Specific 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 expressing that an Elevator object is able to "transport" one passenger, which must be a Person. For example, no Dog, Airplane, or Submarine objects can be stored in the Elevator, had we ever defined such classes in our program.

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

graphics/common.gif

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 assigns a new Person object to passenger; line 19 utilizes some of the functionality of the passenger variable (and hence a Person object) by calling one of its methods, and lines 33 47 define the Person class.

Tip

graphics/bulb.gif

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 } 


Instance Variables Representing the State of an Elevator Object

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 makers of the floor numbers visited by the elevator. The Person object residing inside passenger chooses which floors our Elevator object must visit. A request is obtained from the passenger in line 19 and assigned to the requestedFloor variable.

Abstraction and the Choice of Instance Variables

graphics/common.gif

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 color of type string in the Elevator class, declared as follows:


graphics/05infig02.gif

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 wasteful and complicates our Elevator class unnecessarily.

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:


graphics/05infig03.gif

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.


Enabling Elevator Object to Load a New Passenger

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

graphics/common.gif

Consider the following declaration statement:

 private Person passenger; 

You will come across many different, more or less correct, ways to describe what this sentence is about and, in particular, how to identify an object. In my opinion, the following is a good description but perhaps a bit cumbersome:

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


Creating a New Object with the new Keyword

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

graphics/common.gif

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 performs this initialization task.

To specify that a method is a constructor, it must have the same name as its class. Thus, a constructor for the Person class is called Person() (see line 37). Whenever a new Person object is created with the keyword new, the Person() constructor is automatically called to perform the necessary initializations. This explains the parentheses in line 14, which are always required when any method, and any constructor, is called.



graphics/05infig04.gif

Letting an Elevator Object Receive and Fulfill Passenger Requests

By calling the InitiateNewFloorRequest method, the Elevator object is briefly instructed to:

  • Get a new request from the passenger (line 19)

  • Print out its departure and destination floor (lines 20-21)

  • 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:


graphics/05infig05.gif

Here, we are using the following syntax introduced earlier:


graphics/05infig06.gif

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

graphics/common.gif

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 illustrated in Figure 4.1. The upper half of the figure illustrates the call to NewFloorRequest, following the same graphical style as in Figure 4.1. Step 1 symbolizes the call to NewFloorRequest. There are no formal parameters specified for NewFloorRequest, so no arguments are passed to the method. This step is equivalent to step 1 of the lower half of the figure. After the NewFloorRequest has terminated, it returns a value of type int, as symbolized by the arrow marked 2. The graphics equivalent of step 2 is shown in the lower half of this figure. As seen previously (line 18 of Listing 4.1 in Chapter 4), after step 2 is completed, you can substitute passenger.NewFloorRequest() with this value of type int. Finally, this value can be assigned to the variable requestedFloor with the assignment operator = .

Figure 5.2. Invoking a method of another object.
graphics/05fig02.gif

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

graphics/common.gif

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, surrounding the literal or variable, as shown in the following:

 |-12| = 12 


The use of Math.Abs()

graphics/common.gif

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:

  1. Math.Abs(currentFloor - requestedFloor) Calculate the number of floors traveled on the next elevator ride.

  2. totalFloorsTraveled + Math.Abs(currentFloor - requestedFloor) Add the number of floors traveled on the next elevator ride to the current value of totalFloorsTraveled.

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

The Person Class: The Template for an Elevator Passenger

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 equipping it with an object containing a method that can generate random numbers. This object is kept in the randomNumberGenerator variable. The class this object is instantiated from is found in the .NET class library and is called System.Random(). Line 35 declares the randomNumberGenerator to contain an object of type System.Random.

 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

graphics/common.gif

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 intricate processes for the Person object to decide on the next destination floor.


The Main() Method: Conducting the Simulation

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.

Class Relationships and UML

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-constructed, object-oriented programs, classes collaborate in similar ways each contributing with its own unique functionality for the whole program to work.

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.

The Building-Elevator Relationship

A typical building is composed of many different parts, such as floors, walls, ceilings, a roof, and sometimes elevators. In our case, we can say that the Building we are simulating has an Elevator as one of its parts. This is sometimes referred to as a whole/part relationship. We have implemented this relationship in Listing 5.1 by declaring an instance variable of type Elevator inside the Building class as in line 51:

 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 generally called aggregation, and the accompanying relationships aggregation relationships.

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 indicate that one Building has one Elevator.

Figure 5.3. UML diagram symbolizing composition.
graphics/05fig03.gif

The Unified Modeling Language (UML): The Lingua Franca of OO Modeling

graphics/common.gif

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 potentially have a relationship with any other class defined in the program) and because the format of the pseudocode is much too detailed to provide an overview of a large OO program.

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; accordingly, this book only presents a small subset of UML.

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.


The Elevator-Person Relationship

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 buttons). So even though the passenger in our implementation (for abstraction reasons) has been made a permanent part of the Elevator (the same Person object stays inside the Elevator throughout the simulation), it is not a composition relationship, merely an aggregation. The relationship is illustrated with UML in Figure 5.4. Notice that an open diamond, in contrast to the filled diamond in Figure 5.3, symbolizes aggregation.

Figure 5.4. UML diagram symbolizing aggregation.
graphics/05fig04.gif

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 compartment the methods (behavior) belonging to the class.

Figure 5.5. UML class diagram for Listing 5.1.
graphics/05fig05.gif
Associations

Permanent relationships between classes, such as the aggregation and composition relationships discussed in the previous sections, are generally called structural relationships or, more formally, associations. However, other types of associations exist that are not aggregations (and therefore not compositions either). To give an example of the latter, consider the following scenario: In an attempt to make our elevator simulation more realistic, we change our original list of abstractions shown earlier by allowing many Person objects to enter, travel, and exit the Elevator object instead of just the one Person object that permanently stays there at the moment. Any one Person object could then no longer be said to be a permanent part of the Elevator object, and we would just say that the Elevator class is associated with the Person class. An association is shown with a simple line as in Figure 5.6.

Figure 5.6. Elevator/Person class association.
graphics/05fig06.gif

Other example of associations that are not aggregations are

  • Employee works for Company

  • BankCustomer interacts with the BankTeller

Note

graphics/common.gif

Associations that precisely connect two classes are called binary associations; they are the most common kind of association.



   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

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