Espresso: A Concentrated Introduction to Java
Summary: We consider, at a high level, the basic issues involved in object-oriented problem solving.
As you may have heard, Computer Science is the study of algorithms
(formalized instructions for solving problems) and data. One key aspect
of this study involves the ways in which we represent algorithms. Early
algorithms (by early, I mean long before computers) were often written
as examples. For example, in describing how to compute the volume of a
rectangular hole, one might say
Suppose the hole is 3 feet deep, 4 feet
wide, and 6 feet long. We multiply 4 and 6 to get the area of a cross
section, and then by 3 to get the volume of the hole.
With the advent of automated computing devices, it became important to describe algorithms more carefully, and in such a way that the steps of the algorithm could be automated. In thinking about these descriptions, computer scientists developed four main paradigms for structuring solutions.
In imperative languages, we express algorithms as a sequence of individual steps. Steps traditionally involve basic computation, input, and output. To some, imperative algorithms look very much like the recipes in a cookbook (do this, do that, do this other thing this many times).
In functional languages, we express algorithms by defining and combining functions. Functional algorithms tend to deemphasize precise sequencing of operations; for example, it doesn't matter which argument to a function you compute first.
In object-oriented languages, we express algorithms by defining
objects and having the objects interact with each other.
In declarative languages, we express algorithms by specifying a set of facts or goals, and let the computer determine how to apply those facts or goals to solve the problem at hand.
Right now, object-oriented languages (languages which also have some imperative aspects) currently dominate. Why do programmers like object-oriented languages? There are a variety of reasons to like these languages.
First, object-oriented languages are quite appropriate for programs that model the real world. In writing such models, we can have one object in the program for each object in the world. If your task is to, say, figure out optimal cashier placement at Wal-Mart, such modeling is completely appropriate.
Second, object-oriented languages can simplify parallelization. Since each object can, in effect, work independently, it is possible to put different objects on different processors, and therefore make the program faster.
Third, object-oriented languages are very appropriate for modern graphical user interfaces (GUIs). Programmers have found it much easier to design and implement GUIs when they think of each part of the interface as an object that communicates with other objects in the interface and with objects that provide the underlying computation. For example, we can think of each menu, each button, and each window as a separate object and write the code by which they react to user actions separately.
Finally, it turns out that many core ideas of object-oriented programming make it much easier to write large programs and to reuse code written for previous programs (or for other parts of the same program). We will visit and revisit these efficiencies.
So, what is an object? In object-oriented problem solving, we think of an object as something that collects data and capabilities. Typically, the data within the object are categorized. For example, if we have an object that represents a book, we would identify part of the data as the title, part as the author, part as the publisher, and so on and so forth. Similarly, for an object that represents a menu, we would identify part of the data as the name of the menu and other parts as the items in the menu.
Traditionally, we call the categorized parts of an object the fields or attributes of the object.
But objects do more than collect data. Most objects also provide a variety of capabilities. That is, they can do things, typically things with their internal data (and with values passed in). For example, a book object might provide the text of a page, if you give it a page number, and a menu object might tell you what item was last selected. Some call these capabilities methods (the term we will use). Others refer to them as messages or message handlers.
In a well-designed object-oriented program, the clients of an object (that
is, the other objects that use an object or the programmers who write those
objects) interact with the object and
the fields of the object exclusively through the methods of that object.
Why limit that access? That is, why not give the client direct access
to the fields? Experience suggests that giving the client such access
can create problems in the long term. For example, suppose the client
assumes your object has a field called
lastName. If you later
change the name of that field, the client will no longer work. In almost
every case, the program will be just as efficient if the client does
not directly access the fields, so there is little reason to give the
client that access.
In fact, there are also other reasons to limit access to the internals of an object (not just the fields, but also the details of how each method works). By limiting such access, you absolve the client from having to know how each method works. Clients need only know what your methods do. Consider a rational number (fraction) object. It is likely that you will represent rational numbers as a pair of integers, one for the numerator and one for the denominator. A client that uses your numerator and denominator will need to know not only what names you have chosen for the numerator and denominator, but also particular other details of your representation, such as how you deal with negative numbers and whether you always store rational numbers in simplified form. Rather than forcing the client to know such details, we use methods to separate what one might do with an object (e.g., negate a number) from how we implement those methods.
Traditionally, we call such separation information hiding. Because objects combine methods and data and protect the data from the outside world, we say that objects encapsulate their contents.
One of the first problems any object-oriented language designer encounters is how programmers are to describe the objects that appear in their programs. Most object-oriented languages rely on classes. A class is, in effect, a template for objects. Each class gives the names (and, often, types) of the fields for related objects and the names (and, often, instructions) for the related methods.
For example, a class to represent rational numbers might have two fields, a numerator (integer) and a denominator (integer). It might provide methods to add another rational number to the current rational, subtract another rational number, multiply by another rational number, and so on and so forth.
Similarly, a class to represent books might have a field for the title
(a string), a field for the authors (a list of names, where
is a previously defined class), a field for the pages, and so on and
so forth. That class might provide methods to access the title and
authors (but probably not to change them) and to get pages by number.
Object-oriented programmers quickly realize that the new classes they
build often closely relate to previous classes they've built. For example,
if we are called upon to write a class to represent library books, that
class will be very similar to the class for books, except that the
library book class will include additional fields (such as the call number)
and additional methods (such as checking the book in or out). The
technique called inheritance permits you to define a new class
in terms of an old class, and
automatically inherit all of the
fields and methods of the original class.
At times, when we inherit from another class, we also want to override (change the behavior of) some of the methods of the original class. Most object-oriented languages permit such behavior. For example, suppose we design a square class that inherits from the rectangle class. If the rectangle class includes a method that sets the width, we would want the square class to change that method to set both width and height.
Although some aspects of inheritance could be implemented by the legendary technique of copying and pasting code, real inheritance permits one to make changes to the original class and have those changes automatically propagate to the inheriting class. The automatic reuse and update capabilities associate with inheritance are one of the reasons programmers so prefer object-oriented programming.
The final key aspect of most object-oriented languages is called polymorphism. While inheritance lets you reuse field and method definitions by building new classes from old, polymorphism lets you reuse methods by applying them to new objects. In particular, polymorphism is the notion that you once you write methods that take objects as parameters (and use the methods that those objects provide), then your methods can take any objects as parameters, provided that they provide the appropriate methods.
For example, we know that we can square anything that we can multiply by itself. The square operation can therefore work with integers, real numbers, and complex numbers. It can even work with newly-defined rational numbers, as long as we define the multiply operation.
In this reading, we've considered some key aspects of object-oriented programming languages. When you start to learn a new object-oriented language, you will need to find out how each of these features is implemented. In particular, you will need to figure out
We will cover each of these ideas in our explorations of Java.
Tuesday, 24 January 2006 [Samuel A. Rebelsky]
This page was generated by
Siteweaver on Thu Mar 30 15:24:28 2006.
The source to the page was last modified on Tue Jan 24 13:52:45 2006.
This page may be found at