Chapter 5
Object-Oriented Programming

by Joe Weber

By now, as a programmer you have no doubt heard of a marvelous term called OOP, or object-oriented programming. OOP is truly the hottest method of software development today. It isn't a totally new concept, but it has been a long time in coming to the masses. While Java doesn't impose OOP programming, like some languages (such as SmallTalk), it does embrace it and allows you to dance with the technology seamlessly.

Object-Oriented Programming: A New Way of Thinking

Object-oriented programming is a completely new way of programming and, at the same time, contrary to the belief of some, it is much the same as structured programming. Once you learn and embrace the new terms and concepts associated with object programming, the switch can be so easy you may not even notice you are doing it. You see, the goal of OOP is to provide you, the programmer, with a few new ways to actually write code, and a whole lot of new ways to think about programming.

Once you have embraced the new ways to think about programming the lexical changes, or how you actually write code grammatically, come quite naturally. Unfortunately, truly embracing these changes can take some time. For others, the realization of how OOP works comes in flashes of inspiration. With each new realization you open up a whole new set of programming possibilities.

A Short History on Programming

In order to understand why object-oriented programming is of such great benefit to you as a programmer, it's necessary to take a look at the history of programming as a technology.

In the early days of computing, programming was an extremely procedural process. Each step that the computer needed to take had to be meticulously (and flawlessly) programmed.

If you have ever tried to give another person directions on how to tie their shoes, you found that it was very difficult. Especially if they had never (or acted as if they had never) seen shoelaces before. As a simple exercise, ask a co-worker (one that won't think this is too weird) to take his or her shoes off. Ask that person to only do exactly what you tell them to do and no more. What you will find is that it is necessary to give very precise directions, step-by-step-by-step. "Lift up the left shoelace, move it to the right side below the right shoelace. Pick up the right shoelace," and so on.

If you can grasp the number of instructions you would need to give someone to teach him or her how to tie a shoe, you may be able to grasp what this type of programming was like. Only for programmers, the instructions were a bit more cryptic (and the computer was much less forgiving about imprecise directions). It was necessary to give directions such as "Push the contents of register 1 onto the stack," "Take the contents of the accumulator and place them in register one," and so on.

Procedural Languages

Very soon programmers saw the need for more stylized procedural languages. These procedural languages placed code into blocks called procedures or functions. The goal of each of these blocks was to act like a black box which completed one task or another. For instance, you might create a procedure to write something to the screen, like writeln in Pascal, or printf in C. The purists of this type of programming believed that you could always write these functions without modifying external data. In the example of printf or writeln then, the string that you print to the screen is the same string before and after you print the string out. In essence then, the ideal was not only to build a black box, but when you were done testing it, to weld the box shut.

One of the difficult problems with this method, though, is to write all functions in such a way that they actually do not modify data outside their boundary. This can be very difficult. For instance, what if you want to pass in a value that you want to have updated while it "lives" inside the method (but not one that is returned). Frequently, constraining a procedure in this manner turns out to be too difficult of a restriction. So, as functions began changing data outside their scope (in C this is done by passing a pointer), a problem called coupling began to surface. Because the functions were now changing data outside of their scope, testing became more and more difficult. Coupling meant that each method had to be tested--not only individually, but also to make sure that it wasn't used in such a way that a value it changed wasn't corrupted as a result. In addition, it meant that each black box had to be tested with all of its black boxes in place. If any of those boxes where changed the parent box had to be retested, because the other box may have changed a value and the parent box may not work any longer (starts to sound pretty complicated doesn't it?).

As large programs were developed, the problem of coupling reared its ugly head. Testing these programs begot a whole sub-industry, and software managers lost most of their hair or, if they were lucky enough to keep their hair, you could spot them just as easily because they never cut it.

Structured Development

The next solution was to try structured development. Using structured development, programmers were expected to plan 100 percent of their program before ever writing a single line of code. When a program was developed, huge schematics and flow charts were developed showing the interaction of each function with every other, and how each piece of data flowed through the program. This heavy pre-code work proved to be effective in some cases, but limiting for most. This pitfall may have come in large part because the emphasis in programming became good documentation and not necessarily great design.

In addition, when programmers were pushed to predesign all of their code before actually writing any of it, a bad thing happened. Programming became institutionalized. You see, good programs tended to be as much experimentation as real development. Structured development pulled at this portion of the development cycle. Since the program needed to be completely designed before anything was implemented, programmers were no longer free to sit and experiment with individual portions of the system.

Ahh...Object-Oriented Programming

Finally, along came object-oriented programming. The resulting programming technique at once goes back to procedural development by emphasizing black boxes, embraces structured development (and actually pushes it further), and, most importantly, encourages creative programming design.

Under an OOP paradigm, it is objects which are represented in a system, not just their acquainted data structures. Objects aren't just numbers, like integers and characters; they are also the methods which relate and manipulate the numbers. In OOP programming, rather than passing data around a system openly (such as to a function), messages are passed to and from objects. Rather than taking data and pushing it through a function, a message is passed to an object telling it to perform its task.

Object-oriented programming really isn't all that new; it was developed in the 1970s by the same group of researches at Xerox Parc that brought us GUI (Graphical User Interfaces), ethernet, and a host of other products that today we find commonplace. So why has OOP taken so long to enter into the masses? Well, first OOP requires a paradigm shift in development. In addition, since the computer ends up doing much more work, OOP does tend to require a bit more computing horsepower to obtain the same results. But what a difference those little breaks can make.

Objects themselves are the cornerstone of object-oriented programming. Understanding just the mere concept of objects is perhaps the first and most significant change each programmer who wants to do OOP design must do.

Objects are robust packages which contain both data and methods. Objects are replicateable and adjustable without damaging the predefined code. Instead of being trapped under innumerable potential additional uses, a method's purpose is easily definable, as is the data upon which it will work. As the needs of new programs begin to grow, the method can be replicated or adjusted to fit the needs of the new system taking advantage of the current method, but not necessarily altering it to do this (by overloading or overriding the method).

Objects themselves can be expanded and, by deriving new objects from existing ones, code time is greatly reduced. Equally important, if not more so, debug time is greatly reduced by localizing bugs, because coupling changes are limited, at worst, to new classes.

A Lesson in Objects

As you work, you are familiar with objects all the time: calculators, phones, computers, fax machines, and stereos are all examples of objects in the real world. When you deal with these objects, you don't separate the object from its quantities and methods. For example, when you turn on your stereo, you don't think about the quantities (such as the station number) from the methods (such as turning the dial or making sound). You simply turn it on, select a station, and sit back to listen to the music.

By using object-oriented programming, you can approach the same simplicity of use. As a structured programmer, you are used to creating data structures to hold data, and to defining methods and functions to manipulate this data. Objects, however, take and combine the data with the code. The synergistic relationship that comes out is one object that knows everything necessary to exist and work.

Take a look at an example using your car. When you describe a car, there are a number of important physical factors: the number of people a car can hold, the speed the car is going, the amount of horse power the engine has, the drag coefficient, and so on. In addition, the car has several functional definitions: It accelerates, decelerates, turns, and parks. Neither the physical nor the functional definitions alone embody the definition of your car--it is necessary to define them both.

Traditional Program Design

In a traditional program, you might define a data structure called MyCarData that might look like this:

public class MyCarData {
int weight;
     float speed;
     int hp;
     double dragCoef;
}

Then you would create a set of methods to deal with the data:

public class RunCar {
     public void speedUp(MyCarData m){
     ...
     }
     public void slowDown(MyCarData m){
     ...
     }
     public void stop(MyCarData m){
     ...
     }
}

The OOP Way

In OOP programming, the methods for the car to run and the actual running of the car are combined into one object:

public class Car{
     int weight;
     float speed;
     int hp;
     double dragCoef;
     
     public void speedUp(){
          speed += hp/weight;
     }
     
     public void slowDown(){
speed -= speed * dragCoef;
     }
     
     public void stop(){
          speed=0;
     }
}

Within each of the new methods, there is no need to either reference the variables using dot notation (such as m.speed) or pass in a reference to variables (such as (MyCarData m)). The methods implicitly know about the variables of their own class (these variables are also known as field variables).

Extending Objects Through Inheritance

The next step in developing objects is to create multiple objects based on one "super" object. Return to the car example. A Saturn SL 2 is a car, and yet certainly it has several attributes that not all cars have. When building a car, manufacturers don't typically start from scratch. They know their cars are going to need several things: tires, doors, steering wheels, and more. Frequently, the same parts can be used between cars. Wouldn't it be nice to start with a "car" and build up the specifics of a Saturn, and from there (since each Saturn has its own peculiarities) build up the SL 2?

Inheritance is a feature of OOP programming that enables you to do just this. By inheriting all the common features of a car into a Saturn, it's not necessary to reinvent the object (car) every time.

In addition, by inheriting the features of a car into the Saturn, through an added benefit called polymorphism, the Saturn is also a car. Now that may seem obvious, but the reach and scope of that fact is enormous. Under traditional programming techniques, you would have to separately deal with each type of car--Fords here, GMC there, and so on. Under OOP, though, the features all cars have are encapsulated in the car object. When you inherit car into Ford, GMC, and Saturn you can then polymorph them back to car, and much, if not all, of this work is eliminated.

For instance, say you have a race track program. On the race track you have a green light, yellow light, and red light. Now, each racecar driver comes to the race with a different type of car (I'll be driving that nice red Lamborghini Diablo). Each driver has accessible to him each of the peculiarities of his individual car (such as some fancy accelerator in your Volvo). But, as you put each car on the track, you give a reference of your car to the track itself. Now, the controller of the track doesn't need access to any methods that access that fancy accelerator of yours, or access to the CD player, those methods are individual to each of the cars. However, the person sitting in the control tower does want to be able to tell both drivers to slow down when the yellow light is illuminated. Because both your Volvo and my Lamborghini are cars, the control tower program has received them as cars. So, take a look at this hypothetical code.

Here are our two cars:

class Lamborghini extends Car{
     public void superCharge(){
          for (int x=0;x<infinity;x++)
               speedUp();
     }  
}

class Volvo extends Car{
     CDPlayer cd;

     public void goFaster(){
          while(I_Have_Gas){
               speedUp();
          }
     }     

     public void jam(){
          cd.turnOn();
}     
}

Here is the race track itself.

class RaceTrack {
     Car  theCars[] = new Car[3];
     int  numberOfCars = 0;
     public void addCar(Car newCar){
          theCars[numberOfCars]=newCar;
          numberOfCars++;
     }

     public void yellowLight(){
          for (int x=0;x<numberOfCars;x++)
               theCars[x].slowDown();
     }
}

Now here is the program that puts it all together.

Class RaceProgram{
     Lamborghini me = new Lamborghini();
     Volvo     you = new Volvo();
     RaceTrack rc = new RaceTrack();
     public void start(){
          rc.addCar(me);
          rc.addCar(you);
          while(true){
               if (somethingIsWrong)
                    rc.yellowLight();
          }
     }
}

Now, how can this work? In the RaceProgram you created two different objects, me (of type Lamborghini) and you (of type Volvo). How can you call rc.addCar which takes a Car as a parameter type? The answer lies in polymorphism. Since both of the cars extended Car, they can also be used as Cars as well as their individual types. This means that if you now created yet another type of car (say Saturn) you could call rc.addCar(the Saturn) without having to make any changes to raceTrack whatsoever. Notice that this is true even though Volvo effectively is a different structure since it now also contains a CDPlayer variable!

Objects as Multiple Entities

One of the pitfalls you have probably fallen into as a procedural programmer is thinking of the data in your program as a fixed quantity. A perfect example of this is the screen. Usually procedural programs tend to write something to the (one) screen. The problem with this method is that when you then switch to a windowing environment and have to write to multiple screens, the whole program is in jeopardy. It takes quite a bit of work to go back and change the program so that the right data is written to the window screen.

In contrast, OOP programming treats the screen not as the screen but as a screen. Adding windows is as simple as telling the function it's writing to a different screen object.

This demonstrates one of the aspects of OOP programming that saves the most real programming time immediately. When you become an OOP programmer, you begin thinking of dealing with objects. No matter if there's one or 100 of them, it doesn't effect the program in any way. If you're not familiar with OOP programming, this may not seem to make sense. After all, what you are saying to yourself is, "If I have two screens, when I go to print something to the screen I need to be sure to position it correctly on the correct screen, and pay attention to user interaction to each different window."

Believe it or not, under OOP the need to do this is washed away. Once the elements of a window or screen are abstracted sufficiently, when you write the method it's irrelevant which screen you're writing to. The window object handles all of that for you. This is actually the flip side of polymorphism, since all you care about is that the item is a screen, and not any of the extra capabilities any one particular screen has.

Organizing Code

OOP programming organizes your code elegantly because of two key factors:

Objects and How They Relate to Java Classes

At the heart of Java is support for these objects you have been hearing about. They come in a form called a class. (Actually, there is a Java class called Object which all classes inherit from, so all classes literally are Objects.)

Objects are instances of classes. In this sense, classes can be thought of as a template for creating an object. Take a rectangle as an analogy. A rectangle has an x,y location, height, width, move method, and resize method (for shrinking or enlarging the rectangle). Now, when you write the code for the rectangle you create it in a class. However, to take advantage of the code, you need to create an instance of that class. The instance is a single Rectangle object.

Building a Hierarchy: A Recipe for OOP Design

When setting out to develop an OOP program for the first time, it is often helpful to have a recipe to follow. Developing good OOP structures is a lot like baking a pie. It's first necessary to gather all the ingredients, then begin assembling each portion of the pie.

Break Down Code to Its Smallest Entities

When writing an OOP program, it's first necessary to figure out which ingredients are needed. For instance, if you were writing an arcade game, it would be necessary to figure out everything that would be in that game: creatures, power pieces, the main character, bullets, and so on.

Once you have assembled these pieces, you need to break them down into all of their entities, both data and functional. For this example, if you were setting out to write the arcade game you might create a list like this for the four items:
Piece Entity
Creatures Location in the maze, size, power level, ability to attack, and movability
Power Pieces Must be drawn, location of piece, and amount of power that's given
Bullets Ability to be fired, size of the bullet, and quantity of bullets in a pack
Main Character Ability to receive commands from the user, ability to move around the maze according to these commands, ability to attack, location in the maze, and size

Look for Commonality Between the Entities

The next phase of developing an OOP structure is to look for all the common relationships between each of the entities. For instance, right away you might recognize that the primary difference between the creatures and the main character (aside from how they look) is who controls each. Namely, the creatures are controlled by the computer, and the main character is controlled by the user. Wouldn't it be great if you could write most of the code for both the creatures and the main character once, and then just write separate code for moving them?

If that's how you feel, but you really don't think it could be that easy, keep reading, because treating objects this way is exactly what the OOP paradigm is all about.

Look for Differences Between Entities

The next step is to find the differences between the entities. For instance, the bullets move and the power pieces stay put. Creatures are controlled by the computer, and the main character is controlled by the user. What you are looking for in all of this are relationships that unite and separate all of those entities in your program.

Find the Largest Commonality Between All Entities

The third step is to find the largest common relationship between all the entities in your program. Rarely is it impossible to find any common relationships among all objects. It is possible, however, that from time to time you may find that one entity is just so completely different it doesn't share anything with any other object.

Looking at the game example, what do you see that all four objects have in common? A quick list might be: size, the ability to move around (the power piece doesn't really need to move, but it wouldn't hurt if it could), and location.

Is that all they have in common? No, perhaps the most obvious commonality wasn't even (intentionally) listed before: the ability to be drawn to the screen. This ability is so obvious you may just miss it. Don't forget to look at the obvious.

With these entities, I would suggest creating a class called Draw_Object. The class would contain all the items we've just listed.

Put Remaining Common Objects Together and Repeat

The next phase is to put objects that still have things in common together once you have eliminated the aspects which were just grouped into the previous class. You will use these commonalties to produce another level of classes, each of which will inherit from the class that contains all the completely common information (Draw_Object).

Going back to the example, at this point the power pieces and the bullets probably split from the creatures and the main character. Now, take the remaining objects and repeat the recipe again.

When I went through the next phase, I found that the only real difference between the power pieces and the bullets was their size and how fast they moved (the power pieces at speed 0). Because these were primarily minor differences, I decided to combine them into one class.

When I looked at the creatures and the main character, I decided that the main character contained everything that a creature did plus some, so I would inherit the Creature class in the Main_Character class.

The final class hierarchy is shown in Figure 5.1. Try this on your own, there are countless variations to the chart I developed; see what you come up with.

FIG. 5.1
The hierarchy for the game enables you to save a lot of coding.

Using Objects to Add As Many or Few As Desired

When writing a game, it is often highly desirable to be able to add as many attack creatures as a particular board wants. Because the creature class encapsulates, all that is needed to create a creature and a new board with more creatures is to add those creatures to the list. Again this may seem obvious, but it's extremely powerful, since it means you don't need to create a string of variables like creature1Speed, creature2Speed, creature1Power, creature2Power, and so on.

You can think of this step as if you were creating any other variable. For instance, assuming that you're already a programmer in a different language, you're probably very used to just creating an integer variable any time you need one. Now you can create a whole new creature any time you need one.

Java Isn't a Magic OOP Bullet

The focus of this chapter has been to introduce the concepts of good OOP. The chapter has intentionally avoided complicated coding implementations, the rest of this book should help you fill in that portion.

Now that you have seen many of the fundamentals of OOP programming at a surface level, establish why you went through all of this: Java isn't a magic bullet to creating OOP programs. While Java embraces the OOP paradigm, it is still possible (and not unusual) to write structured programs using Java. It's not unusual to see Java programs written without any acknowledgment of some of the OOP tools just covered, like polymorphism and encapsulation.

By introducing OOP at this stage, hopefully you can break the bad habits of structured programming before they begin. You need to remember that OOP is a different way of thinking as much as it is a different way of programming. Throughout this book, there are applets and applications which are written in both ways. Look for those programs that are broken into multiple pieces. Then when you think you understand OOP, reread this chapter and see if there are any more insights that are brought to mind.