Object-Oriented Programm Ing: Hapter 1
Object-Oriented Programm Ing: Hapter 1
Object-Oriented Programm
ing
Programming languages have
traditionally divided the
world into two parts--data
and operations on data. Data
is static and immutable,
except as the operations may
change it. The procedures
and functions that operate on
data have no lasting state of
their own; they're useful only
in their ability to affect data.
This division is, of course,
grounded in the way
computers work, so it's not
one that you can easily ignore
or push aside. Like the
equally pervasive distinctions
between matter and energy
and between nouns and
verbs, it forms the
background against which we
work. At some point, all
programmers--even object-
oriented programmers--must
lay out the data structures
that their programs will use
and define the functions that
will act on the data.
With a procedural
programming language like
C, that's about all there is to
it. The language may offer
various kinds of support for
organizing data and
functions, but it won't divide
the world any differently.
Functions and data structures
are the basic elements of
design.
Object-oriented programming
doesn't so much dispute this
view of the world as
restructure it at a higher level.
It groups operations and data
into modular units
called objects and lets you
combine objects into
structured networks to form a
complete program. In an
object-oriented programming
language, objects and object
interactions are the basic
elements of design.
Every object has both state
(data) and behavior
(operations on data). In that,
they're not much different
from ordinary physical
objects. It's easy to see how a
mechanical device, such as a
pocket watch or a piano,
embodies both state and
behavior. But almost
anything that's designed to do
a job does too. Even simple
things with no moving parts
such as an ordinary bottle
combine state (how full the
bottle is, whether or not it's
open, how warm its contents
are) with behavior (the ability
to dispense its contents at
various flow rates, to be
opened or closed, to
withstand high or low
temperatures).
It's this resemblance to real
things that gives objects
much of their power and
appeal. They can not only
model components of real
systems, but equally as well
fulfill assigned roles as
components in software
systems.
Interface and Implementation
As humans, we're constantly faced with myriad facts and
impressions that we must make sense of. To do so, we have
to abstract underlying structure away from surface details
and discover the fundamental relations at work.
Abstractions reveal causes and effects, expose patterns and
frameworks, and separate what's important from what's not.
They're at the root of understanding.
To invent programs, you need to be able to capture the
same kinds of abstractions and express them in the program
design.
It's the job of a programming language to help you do this.
The language should facilitate the process of invention and
design by letting you encode abstractions that reveal the
way things work. It should let you make your ideas
concrete in the code you write. Surface details shouldn't
obscure the architecture of your program.
All programming languages provide devices that help
express abstractions. In essence, these devices are ways of
grouping implementation details, hiding them, and giving
them, at least to some extent, a common interface--much as
a mechanical object separates its interface from its
implementation.
Looking at such a unit from the inside, as the
implementor, you'd be concerned with what it's
composed of and how it works. Looking at it from the
outside, as the user, you're concerned only with what it is
and what it does. You can look past the details and think
solely in terms of the role that the unit plays at a higher
level.
Classes
A program can have more than one object of the same kind.
The program that models water usage, for example, might
have several Faucets and WaterPipes and perhaps a handful
of Appliances and Users. Objects of the same kind are said
to belong to the same class. All members of a class are able
to perform the same methods and have matching sets of
instance variables. They also share a common definition;
each kind of object is defined just once.
In this, objects are similar to C structures. Declaring a
structure defines a type. For example, this declaration
struct key {
char *word;
int count;
};
defines the struct key type. Once defined, the structure
name can be used to produce any number of instances of
the type:
struct key a, b, c, d;
Mechanisms of Abstraction
Inheritance
Dynamism
At one time in programming history, the question of how
much memory a program would use was settled when the
source code was compiled and linked. All the memory the
program would ever need was set aside for it as it was
launched. This memory was fixed; it could neither grow
nor shrink.
In hindsight, it's evident what a serious constraint this was.
It limited not only how programs were constructed, but
what you could imagine a program doing. It constrained
design, not just programming technique. Functions
(like malloc()) that dynamically allocate memory as a
program runs opened possibilities that didn't exist before.
Compile-time and link-time constraints are limiting
because they force issues to be decided from information
found in the programmer's source code, rather than from
information obtained from the user as the program runs.
Although dynamic allocation removes one such constraint,
many others, equally as limiting as static memory
allocation, remain. For example, the elements that make up
an application must be matched to data types at compile
time. And the boundaries of an application are typically set
at link time. Every part of the application must be united in
a single executable file. New modules and new types can't
be introduced as the program runs.
Object-oriented programming seeks to overcome these
limitations and to make programs as dynamic and fluid as
possible. It shifts much of the burden of decision making
from compile time and link time to run time. The goal is to
let program users decide what will happen, rather than
constrain their actions artificially by the demands of the
language and the needs of the compiler and linker.
Three kinds of dynamism are especially important for
object-oriented design:
• Dynamic typing, waiting until run time to determine
the class of an object
• Dynamic binding, determining at run time what
method to invoke
• Dynamic loading, adding new components to a
program as it runs
Dynamic Typing
compare = strcasecmp;
else
compare = strcmp;
and call the function through the pointer:
if ( compare(s1, s2) )
. . .
This is akin to what in object-oriented programming is
called dynamic binding, delaying the decision of exactly
which method to perform until the program is running.
Although not all object-oriented languages support it,
dynamic binding can be routinely and transparently
accomplished through messaging. You don't have to go
through the indirection of declaring a pointer and assigning
values to it as shown in the example above. You also don't
have to assign each alternative procedure a different name.
Messages invoke methods indirectly. Every message
expression must find a method implementation to ``call.''
To find that method, the messaging machinery must check
the class of the receiver and locate its implementation of
the method named in the message. When this is done at run
time, the method is dynamically bound to the message.
When it's done by the compiler, the method is statically
bound.
Late Binding
The usual rule has been that, before a program can run, all
its parts must be linked together in one file. When it's
launched, the entire program is loaded into memory at
once.
Some object-oriented programming environments
overcome this constraint and allow different parts of an
executable program to be kept in different files. The
program can be launched in bits and pieces as they're
needed. Each piece is dynamically loaded and linked with
the rest of program as it's launched. User actions can
determine which parts of the program are in memory and
which aren't.
Only the core of a large program needs to be loaded at the
start. Other modules can be added as the user requests their
services. Modules the user doesn't request make no
memory demands on the system.
Dynamic loading raises interesting possibilities. For
example, an entire program wouldn't have to be developed
at once. You could deliver your software in pieces and
update one part of it at a time. You could devise a program
that groups many different tools under a single interface,
and load just the tools the user wants. The program could
even offer sets of alternative tools to do the same job. The
user would select one tool from the set and only that tool
would be loaded. It's not hard to imagine the possibilities.
But because dynamic loading is relatively new, it's harder
to predict its eventual benefits.
Perhaps the most important current benefit of dynamic
loading is that it makes applications extensible. You can
allow others to add to and customize a program you've
designed. All your program needs to do is provide a
framework that others can fill in, then at run time find the
pieces that they've implemented and load them
dynamically.
For example, in the OPENSTEP environment, Interface
Builder dynamically loads custom palettes and inspectors,
and the Workspace Manager(tm) dynamically loads
inspectors for particular file formats. Anyone can design
their own custom palettes and inspectors that these
applications will load and incorporate into themselves.
The main challenge that dynamic loading faces is getting a
newly loaded part of a program to work with parts already
running, especially when the different parts were written by
different people. However, much of this problem
disappears in an object-oriented environment because code
is organized into logical modules with a clear division
between implementation and interface. When classes are
dynamically loaded, nothing in the newly loaded code can
clash with the code already in place. Each class
encapsulates its implementation and has an independent
name space.
In addition, dynamic typing and dynamic binding let
classes designed by others fit effortlessly into the program
you've designed. Once a class is dynamically loaded, it's
treated no differently than any other class. Your code can
send messages to their objects and theirs to yours. Neither
of you has to know what classes the other has implemented.
You need only agree on a communications protocol.
Loading and Linking
Collaboration