Notes on Object-Oriented Design
Given an application to be constructed or a system to be modelled:
- Is there an existing application that can be adapted? Or is there a
framework that can be used? If so, use them! In any case you will be
using existing system classes for the lower-level parts of the application
or simulation.
- Identify the objects involved. If you are using an existing framework,
this will give you strong hints about how to decompose the application into
objects. However, exactly what is an object will depend on the application
or what aspect of the system is to be modelled.
- Identify relations among the objects, in particular part-whole
relations. Other relations may include attribute, container, etc.
- Identify classes of objects, and inheritance relations among these
classes. Are there abstract superclasses that can be defined to capture
common properties?
- Specify messages that the objects should understand, and begin
defining them.
- Heuristic: nouns become objects, verbs become messages. (But see
below -- this is just an initial guideline.)
- Heuristic for inheritance: "is a" relation can be represented using
inheritance. (Example: a chimpanzee is a primate, and a primate is a
mammal, and a mammal is an animal, and an animal is a living thing.)
The part-whole and inheritance relations are often confused by new
object-oriented programmers. Examples of each: we might have an abstract
class Vehicle
and a subclass Bus
(inheritance
relation). A Bus
has parts such as an engine (which might be
an instance of class Engine
), a body, an electrical system,
and so forth. Parts can in turn have subparts. Note that
Engine
isn't a subclass of Bus
however!
Back to the heuristic: a bus is a vehicle, but an engine is not a bus.
First sketch out a general system design. After that many of these steps
are best done interactively -- construct classes and some test instances,
and begin defining and testing methods online. Include method stubs for
methods not yet defined. I often define a method stub using this pattern:
frobnicate: s
"frobnicate this object using s as a milepost"
self notYetImplemented
To do this, add the following definition in class Object:
notYetImplemented
self error: 'this method hasn''t been implemented yet'
The advantage of doing this is that we have placeholders and error checking
(in case we try using one of these methods prematurely. Also we can browse
all senders of notYetImplemented
to see what still needs to be
filled in.
Smalltalk is well-suited to exploratory programming -- programming
systems where there isn't a complete specification yet. You'll need to
rearrange the class hierarchy, change object representation, etc. as you go.
Message protocol; abstract types in object-oriented languages
In Smalltalk we say that a class obeys a certain message protocol.
This is informal and descriptive in Smalltalk. In some other object oriented
languages such as Cecil, this is formalized as an abstract type.
Abstract type need not be the same as class!
For example, all collections understand the Collection protocol
(do:
, collect:
, etc). All objects understand
printing protocol (printOn: aStream
). Very loosely, think of
a protocol as consisting of a set of message names along with the types
(protocols) of the arguments that are expected.
The object-oriented paradigm encourages programmers to concentrate on the
objects being manipulated rather than the code that does the manipulation.
Example: in Scheme there is a single "write" procedure that must work on
all kinds of data. In Smalltalk each kind of object understands the
"printOn:" message.
What should be represented as an object?
It is sometimes appropriate to make objects that represent processes, as
well as program objects representing objects in the system being modelled.
Example: should we make airplane objects that understand a "fly" message?
Or should we make flights into objects?
Or in simulating a billiard game, we might make billiard ball objects that
understand collide
messages. But (less obviously) it might be
appropriate to make Collision objects.
Aphorism: use objects to model entities in the application domain --
however, generally we will also need to introduce other objects that help
us code the application.
Where should behavior go?
Examples: factorial function; lexical scanner. (A lexical scanner is the
part of a compiler that takes an input string and divides it up into
tokens. For example the Smalltalk input string '(x+5.3) printOn: s' would
be divided into the tokens x, +, 5.3, etc.)
Should we implement the factorial function as a message to integers, or as
a separate FactorialComputer object? Should we implement the scanner as an
operation on Strings, or make a separate Scanner object?
Probably factorial should be a message to integers, but there should be
separate scanner object. We could still allow the scanner to be accessed
by a scan message -- but the code would be something like 'aString scan'
-- but the code would just be
scan
| scanner |
scanner := LexicalScanner new.
scanner target: self.
^scanner tokens
Inspector: all objects understand the inspect
message -- but
the details of the inspector window etc are all in an inspector object.
Uses of subtyping
- subtyping for specialization (Vehicle: Bus, Truck, Car)
- subtyping for implementation (Number: Float/Integer)
- subtyping for combination (ReadStream, WriteStream: ReadWriteStream).
This involves multiple inheritance; sometimes misused in place of part/whole
relationship. Not available in Smalltalk or Java, but is available in C++
and CLOS.
Nonstandard uses of subtyping:
- subtyping for generalization (Circle, Ellipse)
- subtyping for variance
Object-Oriented Design Methodologies
Many of these: Booch, Rumbaugh, etc etc.
Checklists of things to do; diagramming methods, etc. See e.g.
Author: Booch, Grady
Title: Object-Oriented Design with Applications
Edition: 2nd ed.
Publisher: Benjamin-Cummings Publishing Company
Year: 1994
Pages: 589p.
ISBN/Price: 0-8053-5340-2 Cloth Text $52.75 (Ingram Price), $51.75 (Net),
$51.95
Frameworks
Reusable collections of classes -- subclass from these and adapt them to
construct an application. Examples: the Model-View-Controller framework;
the file system.
These need to be built with care: what are the right superclasses? what
are the right method stubs that let you specialize behavior easily?
Generally, you have to build a couple of applications first to see what the
right generalizations are.
Patterns
Patterns in object-oriented programming are idiomatic and recurring
structures of objects. A recent book is a catalog of 23 design patterns:
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, "Design
Patterns: Elements of Reusable Object-Oriented Software," Addison-Wesley, 1994.
(An intellectual antecedent is the work in architecture of Christopher
Alexander.)
Examples:
- proxy
- command (make an object that represents a command -- the object can
then understand messages such as
do
and undo
.
- observer (used in model-view-controller)
- state (uses indirection to achieve different behavior for an object
depending on its state)
- prototype
- singleton
- decorator (wrapper)
See also
the patterns home page.
Object-oriented design and the pinball assignment
What are the objects? pinballgame, ball, flippers, scoring device. What
are the inheritance relationships? Hint in assignment: make a Bumper
class, with subclasses Flipper, Wall, ScoringThing, BlackHole, etc. Don't
confuse inheritance and part/whole relationships -- for example, Ball is
not a subclass of PinballGame!
Are there frameworks to use? Yes - AnimatedObject, AnimationFrame, window
system.