All
programming languages provide abstractions. It can be argued that the
complexity of the problems you’re able to solve is directly related to
the kind and quality of abstraction.
By “kind” I mean “what is it that you are abstracting?”
Assembly language is a small abstraction of the underlying machine. Many
so-called “imperative” languages that followed (such as FORTRAN,
BASIC, and C) were abstractions of assembly language. These languages are big
improvements over assembly language, but their primary abstraction still
requires you to think in terms of the structure of the computer rather than the
structure of the problem you are trying to solve. The programmer must establish
the association between the machine model (in the “solution space,”
which is the place where you’re modeling that problem, such as a
computer) and the model of the problem that is actually being solved (in the
“problem space,” which is the place where the problem actually
exists). The effort required to perform this mapping, and the fact that it is
extrinsic to the programming language, produces programs that are difficult to
write and expensive to maintain, and as a side effect created the entire
“programming methods” industry.
The
alternative to modeling the machine is to model the problem you’re trying
to solve. Early languages such as LISP and APL chose particular views of the
world (“all problems are ultimately lists” or “all problems
are algorithmic”). PROLOG casts all problems into chains of decisions.
Languages have been created for constraint-based programming and for
programming exclusively by manipulating graphical symbols. (The latter proved
to be too restrictive.) Each of these approaches is a good solution to the
particular class of problem they’re designed to solve, but when you step
outside of that domain they become awkward.
The
object-oriented approach goes a step further by providing tools for the
programmer to represent elements in the problem
space. This representation is general enough that the programmer is not
constrained to any particular type of problem. We refer to the elements in the
problem space and their representations in the solution space as
“objects.” (Of course, you will also need other objects that
don’t have problem-space analogs.) The idea is that the program is
allowed to adapt itself to the lingo of the problem by adding new types of
objects, so when you read the code describing the solution, you’re
reading words that also express the problem. This is a more flexible and
powerful language abstraction than what we’ve had before. Thus OOP allows
you to describe the problem in terms of the problem, rather than in terms of
the computer where the solution will run. There’s still a connection back
to the computer, though. Each object looks quite a bit like a little computer;
it has a state, and it has operations that you can ask it to perform. However,
this doesn’t seem like such a bad analogy to objects in the real world;
they all have characteristics and behaviors.
Some
language designers have decided that object-oriented programming itself is not
adequate to easily solve all programming problems, and advocate the combination
of various approaches into
multiparadigm
programming languages.
[6]
Alan
Kay summarized five basic characteristics of Smalltalk,
the first successful object-oriented language and one of the languages upon
which C++ is based. These characteristics represent a pure approach to
object-oriented programming:
Everything
is an object.
Think of an object as a fancy variable; it stores data, but you can “make
requests” to that object, asking it to perform operations on itself. In
theory, you can take any conceptual component in the problem you’re
trying to solve (dogs, buildings, services, etc.) and represent it as an object
in your program.
A
program is a bunch of objects telling each other what to do by sending messages
.
To make a request of an object, you “send a message” to that
object. More concretely, you can think of a message as a request to call a
function that belongs to a particular object.
Each
object has its own memory made up of other objects
.
Put another way, you create a new kind of object by making a package containing
existing objects. Thus, you can build complexity in a program while hiding it
behind the simplicity of objects.
Every
object has a type
.
Using the parlance, each object is an
instance
of a
class,
where “class” is synonymous with “type.” The most
important distinguishing characteristic of a class is “what messages can
you send to it?”
All
objects of a particular type can receive the same messages
.
This is actually a very loaded statement, as you will see later. Because an
object of type “circle” is also an object of type
“shape,” a circle is guaranteed to receive shape messages. This
means you can write code that talks to shapes and automatically handle anything
that fits the description of a shape. This
substitutability
is one of the most powerful concepts in OOP.
[6]
See
Multiparadigm
Programming in Leda
by Timothy Budd (Addison-Wesley 1995).