Programming languages, like their spoken-language kin, evolve over time. They are constantly refined and focused to meet the ever-changing needs of their users. Like other modern programming languages such as C++, Java is an amalgamation of all the techniques developed over the years. Therefore, we'll start exploring object-oriented programming (OOP) by briefly looking at the history of programming languages. Knowing where object-oriented ideas came from will help you to better understand why they are an important part of modern programming languages. Once you understand why OOP was developed, you'll learn exactly what makes a programming language object-oriented.
Back in the dark ages of computing, technicians programmed computers by flipping banks of switches, with each switch representing a single bit of information. In those days, even the simple programs required agonizing patience and precision. As the need for more sophisticated programs grew, so did the need for better ways to write these programs. The need to make computer programming quicker and simpler spurred the invention of assembly language and, shortly thereafter, high-level languages such as FORTRAN.
High-level languages enable programmers to use English-like commands in their programs and to be less concerned with the details of programming a computer and more concerned with the tasks that need to be completed. For example, in assembly language-a low-level language-it might take several instructions to display a line of text on the screen. In a high-level language, there's usually a single command such as PRINT that accomplishes this task.
With the advent of high-level languages, programming became accessible to more people; writing program code was no longer exclusively the domain of specially trained scientists. As a result, computing was used in increasingly complex roles. It was soon clear, however, that a more efficient way of programming was needed, one that would eliminate the obscure and complex "spaghetti code" that the early languages produced.
Programmers needed a new way of using high-level languages, one that enabled them to partition their programs into logical sections that represented the general tasks to be completed. Thus, the structured-programming paradigm was born. Structured programming encourages a top-down approach to programming, in which the programmer focuses on the general functions that a program must accomplish rather than the details of how those functions are implemented. When programmers think and program in top-down fashion, they can more easily handle large projects without producing tangled code.
For an analogy, consider an everyday task such as cleaning a house. If you wanted to write out the steps needed to complete this task, you'd write something like this:
Go to the living room.
Dust the coffee table.
Dust the end tables.
Vacuum the rug.
Go to the Kitchen.
Wash the dishes.
Wipe the counters.
Clean the stove.
Wipe off the refrigerator.
Sweep the floor.
Go to the bedroom.
Make the bed.
Dust the bureau.
Vacuum the rug.
The preceding list of steps is similar, in theory, to how you'd program a computer without using a top-down approach. Using the top-down programming approach, you'd revise the "program" as follows:
TOP LEVEL
Clean the Living Room.
Clean the Kitchen.
Clean the Bedroom.
SECOND LEVEL
Clean the Living Room
START
Go to the living room.
Dust the coffee table.
Dust the end tables.
Vacuum the rug.
END
Clean the Kitchen
START
Go to the Kitchen.
Wash the dishes.
Wipe the counters.
Clean the stove.
Wipe off the refrigerator.
Sweep the floor.
END
Clean the Bedroom
START
Go to the bedroom.
Make the bed.
Dust the bureau.
Vacuum the rug.
END
Now, if you're only interested in seeing what the "program" does, you can glance at the top level and see that these are instructions for cleaning the living room, kitchen, and bedroom. If that's all you need to know, you need to look no further. If, however, you want to know exactly how to clean the living room, you can go down one level in the top-down structure and find the detailed instructions for cleaning the living room. Yes, the top-down approach tends to make programs longer, but it also adds clarity to the program, because you can hide the details until you really need them.
Today, the need for efficient programming methods is more important than ever. The size of the average computer program has grown dramatically and now consists of hundreds of thousands of code lines. (It's rumored that Windows 95 comprises as much as 15 million lines of code. It boggles the mind!) With these huge programs, reusability is critical. Again, a better way of programming is needed-and that better way is object-oriented programming.
The world consists of many objects, most of which manipulate other objects or data. For example, a car is an object that manipulates its speed and direction to transport people to a different location. This car object encapsulates all the functions and data that it needs to get its job done. It has a switch to turn it on, a wheel to control its direction, and brakes to slow it down. These functions directly manipulate the car's data, including direction, position, and speed.
When you travel in a car, however, you don't have to know the details of how these operations work. To stop a car, for example, you simply step on the brake pedal. You don't have to know how the pedal stops the car. You simply know that it works.
All these functions and data work together to define the object called a car. Moreover, all these functions work very similarly from one car to the next. You're not likely to confuse a car with a dishwasher, a tree, or a playground. A car is a complete unit-an object with unique properties.
You can also think of a computer program as consisting of objects. Instead of thinking of a piece of code that, for example, draws a rectangle on-screen, and another piece of code that fills the rectangle with text, and still another piece of code that enables you to move the rectangle around the screen, you can think of a single object: a window. This window object contains all the code that it needs in order to operate. Moreover, it also contains all the data that it needs. This is the philosophy behind OOP.
Object-oriented programming enables you to think of program elements as objects. In the case of a window object, you don't need to know the details of how it works, nor do you need to know about the window's private data fields. You need to know only how to call the various functions (called methods in Java) that make the window operate. Consider the car object discussed in the previous section. To drive a car, you don't have to know the details of how a car works. You need to know only how to drive it. What's going on under the hood is none of your business. (And, if you casually try to make it your business, plan to face an amused mechanic who will have to straighten out your mess!)
But OOP is a lot more than just a way to hide the details of a program. To learn about OOP, you need to understand three main concepts that are the backbone of OOP. These concepts, which are covered in the following sections, are: encapsulation, inheritance, and polymorphism.
NOTE |
If you're new to programming, you might want to stop reading at this point and come back to this chapter after you've studied Chapters 5 through 14. It's not possible to discuss concepts such as encapsulation, inheritance, and polymorphism without dealing with such subjects as data types, variables, and variable scope. If these terms are unfamiliar, move on now to Chapter 5 |
One major difference between conventional structured programming and object-oriented programming is a handy thing called encapsulation. Encapsulation enables you to hide, inside the object, both the data fields and the methods that act on that data. (In fact, data fields and methods are the two main elements of an object in the Java programming language.) After you do this, you can control access to the data, forcing programs to retrieve or modify data only through the object's interface. In strict object-oriented design, an object's data is always private to the object. Other parts of a program should never have direct access to that data.
How does this data-hiding differ from a structured-programming approach? After all, you can always hide data inside functions, just by making that data local to the function. A problem arises, however, when you want to make the data of one function available to other functions. The way to do this in a structured program is to make the data global to the program, which gives any function access to it. It seems that you could use another level of scope-one that would make your data global to the functions that need it-but still prevent other functions from gaining access. Encapsulation does just that. In an object, the encapsulated data members are global to the object's methods, yet they are local to the object. They are not global variables.
An object is just an instance of a data type. For example, when you declare a variable of type int, you're creating an instance of the int data type. A class is like a data type in that it is the blueprint upon which an object is based. When you need a new object in a program, you create a class, which is a kind of template for the object. Then, in your program, you create an instance of the class. This instance is called an object.
Classes are really nothing more than user-defined data types. As with any data type, you can have as many instances of the class as you want. For example, you can have more than one window in a Windows application, each with its own contents.
For example, think again about the integer data type (int). It's absurd to think that a program can have only one integer. You can declare many integers, just about all you want. The same is true of classes. After you define a new class, you can create many instances of the class. Each instance (called an object) normally has full access to the class's methods and gets its own copy of the data members.
Inheritance enables you to create a class that is similar to a previously defined class, but one that still has some of its own properties. Consider a car-simulation program. Suppose that you have a class for a regular car, but now you want to create a car that has a high-speed passing gear. In a traditional program, you might have to modify the existing code extensively and might introduce bugs into code that worked fine before your changes. To avoid these hassles, you use the object-oriented approach: Create a new class by inheritance. This new class inherits all the data and methods from the tested base class. (You can control the level of inheritance with the public, private, and protected keywords. You'll see how all this works with Java in Chapter 14, "Classes.") Now, you only need to worry about testing the new code you added to the derived class.
NOTE |
The designers of OOP languages didn't pick the word "inheritance" out of a hat. Think of how human children inherit many of their characteristics from their parents. But the children also have characteristics that are uniquely their own. In object-oriented programming, you can think of a base class as a parent and a derived class as a child. |
The last major feature of object-oriented programming is polymorphism. By using polymorphism, you can create new objects that perform the same functions as the base object but which perform one or more of these functions in a different way. For example, you may have a shape object that draws a circle on the screen. By using polymorphism, you can create a shape object that draws a rectangle instead. You do this by creating a new version of the method that draws the shape on the screen. Both the old circle-drawing and the new rectangle-drawing method have the same name (such as DrawShape()) but accomplish the drawing in a different way.
Although you won't actually start using Java classes until later in this book, this is a good time to look at OOP concepts in a general way. As an example, you'll extend the car metaphor you read earlier this chapter.
In that section I described a car as an object having several characteristics (direction, position, and speed) and several means (steering wheel, gas pedal, and brakes) to act on those characteristics. In terms of constructing a class for a car object, you can think of direction, position, and speed as the class's data fields and the steering wheel, gas pedal, and brakes as representing the class's methods.
The first step in creating an object is to define its class. For now, you'll use pseudo-code to create a Car class. You'll learn about Java classes in Chapter 14, "Classes." The base Car class might look like Listing 4.1.
Listing 4.1 LST4_1.TXT: The pseudocode for a Base Car Class.
class Car { data direction; data position; data speed; method Steer(); method PressGasPedal(); method PressBrake(); }
In this base Car class, a car is defined by its direction (which way its pointed), position (where it's located), and speed. These three data fields can be manipulated by the three methods Steer(), PressGasPedal(), and PressBrake(). The Steer() method changes the car's direction, whereas the PressGasPedal() and PressBrake() change the car's speed. The car's position is affected by all three methods, as well as by the direction and speed settings.
The data fields and methods are all encapsulated inside the class. Moreover, the data fields are private to the class, meaning that they cannot be directly accessed from outside of the class. Only the class's three methods can access the data fields. In short, Listing 4.1 not only shows what a class might look like, it also shows how encapsulation works.
Now, suppose you want to create a new car that has a special passing gear. To do this, you can use OOP inheritance to derive a new class from the Car base class. Listing 4.2 is the pseudocode for this new class.
Listing 4.2 LST4_1.TXT: Deriving a New Class Using Inheritance.
Class PassingCar inherits from Car { method Pass(); }
You may be surprised to see how small this new class is. It's small because it implicitly inherits all the data fields and methods from the Car base class. That is, not only does the PassingCar class have a method called Pass(), but it also has the direction, position, and speed data fields, as well as the Steer(), PressGasPedal(), and PressBrake() methods. The PassingCar class can use all these data fields and methods exactly as if they were explicitly defined in Listing 4.2. This is an example of inheritance.
The last OOP concept that you'll apply to the car classes is polymorphism. Suppose that you now decide that you want a new kind of car that has all the characteristics of a PassingCar, except that its passing gear is twice as fast as PassingCar's. You can solve this problem as shown in Listing 4.3.
Listing 4.3 LST4_3.TXT: Using Polymorphism to Create a Faster Car.
class FastCar inherits from PassingCar { method Pass(); }
The FastCar class looks exactly like the original PassingCar class. However, rather than just inheriting the Pass() method, it defines its own version. This new version makes the car move twice as fast as PassingCar's Pass() method does (the code that actually implements each method is not shown). In this way, the FastCar class implements the same functionality as the PassingCar() class, but it implements that functionality a little differently.
NOTE |
Because the FastCar class inherits from PassingCar, which itself inherits from Car, a FastCar also inherits all the data fields and methods of the Car class. There are ways that you can control how inheritance works (using the public, protected, and private keywords), but you won't get into that until much later in this book. |
Java is an object-oriented language, meaning that it can not only enable you to organize your program code into logical units called objects, but also that you can take advantage of encapsulation, inheritance, and polymorphism. Learning OOP, however, can be a little tricky. If you're a novice programmer, this chapter has probably left you confused. If so, read on to learn more about the Java language. Once you start writing Java programs, much of what you read here will make more sense. After finishing this part of the book, you might want to reread this chapter and so reinforce any concepts that may be shady now.