Designing Objects
All of the code presented in this chapter is available at http://guzdial.cc.gatech.edu/st/clock.st.
Design Process
- Object-oriented analysis
- Object-oriented design
- Object-oriented programming
Clock Example
Subclassing: AlarmClock Example
Reuse: VCR Example
Reuse: AppointmentBook Example
Objects Are Different
My Experience with the Pluggable WebServer (PWS) (http://www.cc.gatech.edu/fac/mark.guzdial/squeak/pws)
- Distribute responsibility. A good design does not have one God Object through which all services must pass. This is probably the most common error in object-oriented design.
- Get the responsibilities right. When you're deciding what object performs what service, ask yourself, "Whose responsibility is this?"
- Use the power of object-oriented languages: Use instances not classes. Create instances and use references to those instances to access the services of those instances.
The Object-Oriented Design Process
- Object-oriented analysis: In the analysis stage, your goal is to understand the domain. What are the objects in this domain? What are the services and attributes of each? Analysis is completely programming language independent.
- Object-oriented design: In the design stage, your goal is to build the classes.
- Object-oriented programming: Finally, you build the code.
Not a linear process
Object-Oriented Analysis Stage
Throughout all of analysis, what you're really doing is figuring out what you're doing.
I use two kinds of activities in my object-oriented analyses.
The first one is simply brainstorming.
Second one is CRC Card Analysis
Brainstorming
Try to write down all the objects ( NOUNS !)that you can think of that relate to the domain in which you are working.
Filtering
- Separate those objects that have to do with the problem domain from those having to do with the human interface.
- Are some of the candidate objects really attributes of other candidate objects?
- Are some of the candidate objects really subclasses of some other candidate objects?
Final filter of your brainstormed candidate objects: Which of these objects are really ones that you want to be dealing with?
CRC Cards
Ward Cunningham (also of WikiWikiWeb!) and Kent Beck
Typically, CRC cards are common 3x5 index cards.
(I've created a Squeak window definition that you can use to play with cards on the Squeak desktop (http://guzdial.cc.gatech.edu/st/CRCWindow.st).)
A CRC Card has three fields:
- A Class name,
- A list of Responsibilities for the class, and
- The Collaborators with this class that help to achieve those responsibilities.
Using CRC Cards
- Write the name of each class that you plan to define at the top of its own card.
- Start writing responsibilities for that class on the card.
- Invent some scenarios -- functions or sets of activities that go together that you will want your set of objects to handle.
- Walk through each of your scenarios and use the cards to identify who is responsible for what.
- What object will get the initial message that starts the scenario process? Lay that card down first on a table or some other large playing surface.
- What will the object be responsible for?
- What other objects will that first object need to work with?
- As objects leave the scenario, pick the card back up, and lay down new cards as their objects enter the scenario.
Benefits of CRC Card Analysis
The CRC cards can help you check that you've covered all your responsibilities, that you understand the interactions between the orjects, and that the responsibilities make sense.
The cards form a useful record of your early design thoughts.
You can play with your cards in a group!
Object Oriented Design Stage
Goal: A description detailed enough to code from.
- A class diagram defining the attributes and services of each class and formally identifying the connections between each class.
- A description of what each service is supposed to do.
Class Diagrams
Not UML, Coad.
Tools: Playground http://www.oi.com and BOOST http://www.cc.gatech.edu/gvu/edtech/BOOST/home.html
Class Diagrams
Here's an example Coad class diagram using Joe the Box as an example.
Each class becomes a rectangle in this notation: Name, attributes, services.
The double bar around the rectangle indicates that these are concrete classes. A concrete class is one that you will actually create instances of. An abstract class is one that you create only to define functionality that you will inherit in other classes. You never create instances of an abstract class.
The lines between the class boxes indicate relationships. The symbol that looks like a logical AND indicates a generalization-specialization or IsA relationship.
We say that the relationship between NamedBox and Name would be whole-part or HasA.
Lines for "just talking"
First Design: Clock
So what do you think goes into a clock?
First Design: Clock
What goes into a clock?
- A face for the clock, to read the time from.
- Some kind of internal ticker that keeps the clock moving at a regular interval.
- Probably some internal representation of hours, minutes, and seconds. (Maybe lower?)
- Some of way of mapping between the internal ticker into seconds, and then every 60 of those, into mintues, and then every 60 of those, into hours.
- Some kind of knob for setting the clock.
First Design: Clock
Instance variables
- For tracking time: seconds , minutes , and hours .
- For deciding how to display the time (e.g., 12 or 24 hours): displayFormat .
Methods
- For accessing the time variables: getSeconds, setSeconds, getMinutes, setMinutes, getHours, setHours .
- For ticking the clock: nextSecond .
- For getting the time in the appropriate display format: display (the time), setFormat .
- Maybe something for getting the time in some kind of raw form, and for setting the time: getTime, setTime .
Mistakes We Just Made
- When did we decide to do only a single class, Clock ?
- We started out with data, listing all the instance variables, rather than thinking about what our class should do , and even before that, what its responsibilities are.
- We jumped to using words that are specific to given programming languages.
Brainstorming a Clock
- A Display which would be responsible for displaying the time,
- A Time for tracking hours, minutes, seconds and their relationships,
- A Ticker or SecondsTimer for providing constant time pulses,
- A Clock which would be responsible for tracking time and displaying it on request.
Scenarios for CRC Cards for a Clock
- When the Ticker ticks out a time pulse, an internal counter must increment, which must increment seconds, minutes, and hours increments as needed.
- When a display is requested, the appropriate format for the display must be determine, and the time must be gathered, then converted (as necessary) to the appropriate format.
Ticker Scenario in CRC Cards
Control begins with the Ticker.
Ticker Scenario in CRC Cards
Now the Clock enters the picture. The Clock must increment the representation of Time , so that is a collaborator.
Ticker Scenario in CRC Cards
Now Time is informed that a second has gone by, so it must increment its seconds representation, which may in turn trigger the representations for minutes and hours.
Display Time Scenario in CRC Cards
- When a display is requested, the Clock needs to get the time, so Time is a collaborator.

- Time must return the time in a format that the Clock can manipulate, since it will be the Clock's responsibility to format it.

- Time is then done, and the Clock must format the time appropriately, and then return it to the caller for display.
Design of a Clock
Clock : The Clock has to be able to set the displayFormat (and thus know it, too) and return time in a given format. It needs to be able to respond to a nextSecond , and pass that on to Time. It needs to know about Time.
It does not actually collaborate with the SecondsTimer, so the Clock doesn't really need to know about that object. I found, however, that it becomes useful for the Clock to know its timer when you think about starting and stopping the Clock. Starting and stopping the clock is really about asking the timer to stop firing.
Design of a Clock
SecondsTimer :The SecondsTimer has to know its clock , in order to be able to tell it when a second has passed. The SecondsTimer has to be able to turn on and off (start and stop). It is probably going to use some kind of external process to generate the timing signals, so it will need to know its process .
Design of a Clock
Time : Time must be able to track the hours, minutes, and seconds. It must be able to increment the number of seconds, and have that addition flow into the other units, too. It really doesn't need to know about any other objects.
Design of a Clock

Delegating current time
Alternatives
How else could we have done this?
SecondsTimer updates Time?
Time handles formatting?
Programming the Clock
Object subclass: #Clock
instanceVariableNames: 'time timer displayFormat '
classVariableNames: ''
poolDictionaries: ''
category: 'ClockWorks'!
Object subclass: #SecondsTimer
instanceVariableNames: 'clock process '
classVariableNames: ''
poolDictionaries: ''
category: 'ClockWorks'!
Now we can be language dependent: Time is given.
Programming the SecondsTimer
Interesting part: The timing process
SecondsTimer methodsFor: 'time management' stamp: 'mjg 9/27/1998 14:12'!
startTicking
process := [[true] whileTrue: [(Delay forSeconds: 1) wait. clock nextSecond.]] newProcess.
process priority: (Processor userBackgroundPriority).
process resume.! !
SecondsTimer methodsFor: 'time management' stamp: 'mjg 9/26/1998 17:04'!
stopTicking
process terminate.! !
Programming the Clock
Starting and stopping the Timer
Clock methodsFor: 'time management' stamp: 'mjg 9/27/1998 14:21'!
start
timer isNil ifFalse: [timer stopTicking. "Stop one if already existing."].
timer _ SecondsTimer new.
timer clock: self.
timer startTicking.
!
Clock methodsFor: 'time management' stamp: 'mjg 9/27/1998 14:37'!
stop
timer isNil ifFalse: [timer stopTicking].
timer _ nil.
!
Programming the Clock
Setting the Time
Clock methodsFor: 'time management' stamp: 'mjg 9/26/1998 16:56'!
setTime: aString
time _ Time readFrom: (ReadStream on: aString).
!
Programming the Clock
Incrementing Seconds
Clock methodsFor: 'time management' stamp: 'mjg 9/27/1998 14:10'!
nextSecond
time _ time addTime: (Time fromSeconds: 1)
! !
Programming the Clock
Display Format
Clock methodsFor: 'time management' stamp: 'mjg 9/26/1998 16:57'!
displayFormat: aType
"aType should be '24' or '12'"
displayFormat _ aType
!
Clock methodsFor: 'time management' stamp: 'mjg 9/26/1998 17:22'!
display
"Display the time in a given format"
| hours minutes seconds |
hours _ time hours printString.
minutes _ time minutes printString.
(minutes size < 2) ifTrue: [minutes _ '0',minutes]. "Must be two digits"
seconds _ time seconds printString.
(seconds size < 2) ifTrue: [seconds _ '0',seconds].
(displayFormat = '12')
ifTrue: [(hours asNumber > 12)
ifTrue: [^((hours asNumber - 12) printString),':',minutes,':',
seconds,' pm'].
(hours asNumber < 12)
ifTrue: [^hours,':',minutes,':',seconds,' am']
ifFalse: ["Exactly 12 must be printed as pm"
^hours,':',minutes ,':',seconds,' pm']]
ifFalse: ["24-hour time is the default if no displayFormat is set"
^hours,':',minutes,':',seconds].! !
Trying out the clock
cl := Clock new.
cl displayFormat: '12'.
cl setTime: '2:05 pm'.
cl start.
Transcript show: cl display .
cl stop .
Specializing Clock as an AlarmClock
What should be in an AlarmClock?
Well?
Specializing Clock as an AlarmClock
- Depends on its generalization to inherit the standard nextSecond behavior which increments the time.
- Needs a new Time object to represent alarm time.
- When the alarm time arrives, Needs to execute the alarm behavior.
Specializing Clock as an AlarmClock
Make the alarm behavior a general block
Didn't that gag?
A block exists in Smalltalk only!
Alarm should actually be defined as a class now, make it a Smalltalk-specific thingie as last as possible.
Programming the AlarmClock
Clock subclass: #AlarmClock
instanceVariableNames: 'alarmTime alarmBlock '
classVariableNames: ''
poolDictionaries: ''
category: 'ClockWorks'!
Programming the AlarmClock
Handling Alarm Time
AlarmClock methodsFor: 'time management' stamp: 'mjg 9/27/1998 13:58'!
nextSecond
super nextSecond.
(time = alarmTime) ifTrue: [alarmBlock value].
!
AlarmClock methodsFor: 'time management' stamp: 'mjg 9/27/1998 13:58'!
setAlarmTime: aString
alarmTime _ Time readFrom: (ReadStream on: aString).
!
Testing the AlarmClock
cl _ AlarmClock new.
cl setTime: '2:04 pm'.
cl alarmBlock: [3 timesRepeat: [Smalltalk beep. Transcript show: 'ALARM!']].
cl setAlarmTime: '2:06 pm'.
cl start
cl stop .
Reuse in a VCR
Scenario: Starting and stopping a recording
- The VCRRecorder has an alarm clock for starting the recording session. When it goes off, it tells the VCR to go to the appropriate channel, and start recording.
- The VCR knows how to change channels and record.
- The VCRRecorder also has an alarm clock for ending the recording session. When it goes off, it tells the VCR to stop.
The Power of Aggregation
Real power of O-O
Look at where everything is, yet little knows about anything else.
Side Trip: Good Design
Characteristics of a Good Object-Oriented Design
- It's general and based on real world artifacts. That makes the design reusable.
- Information access is enough . Objects don't need information that they can't get to. Objects can get to the information they need: Either directly, or by asking one of the objects that they can access directly.
- Responsibility, control, and communication is distributed. Not one object does everything.
- It describes the world , not a program.
- It defines objects as nouns, not functions and not managers.
- The relationship between a subclass and a superclass is always an IsA relationship.
- Attributes and services are factored out as high in the class hierarchy as possible.
- There should be little or no redundancy: Code should appear only once.
- It avoids computer science terms like "linked list." The real world doesn't have linked lists in it. Implementations of models of the real world do.
- Include the right level of detail for the model that you need. Yes, everything in the world is made up of molecules, but most problems don't require you to model each and every molecule.
Reuse in an AppointmentBook
What are the new objects in an AppointmentBook?
- Maybe an AppointmentBook to track appointments.
- A Calendar to associate appointments with their days.
- Which suggests an Appointment which is responsible for alerting the user to a given appointment at the right time.
- Maybe the AlarmClock for triggering the Appointment.
- An AppointmentQueue so as to sort the appointments and set up the AlarmClock for the next appointment.
Reuse in an AppointmentBook
Filtering and Designing
- Appointments know their alarm and the date on which they should be active. They don't need to know their time -- that's delegated to the alarm.
- AppointBook knows all the appointments, and it is responsible for turning them on for a given day (and off at the end of the day), and to make an appointment for a given day.
Programming the AppointmentBook
AppointmentBook methodsFor: 'initialization' stamp: 'mjg 9/27/1998 14:07'!
initialize
appointments _ OrderedCollection new.
!
OrderedCollection: Like an extendable Array (or Java Vector )
There are lots of collections: Bags (no ordering, duplications allowed), Sets (no ordering, no duplicates), SortedCollections , etc.
Each is optimized for its characteristics
Programming the AppointmentBook
AppointmentBook methodsFor: 'appointments' stamp: 'mjg 9/27/1998 14:29'!
makeAppointment: aDescription for: aDate at: aTime
| a |
a _ Appointment new.
a description: aDescription.
a date: (Date readFrom: (ReadStream on: aDate)).
a alarm: aTime.
appointments add: a.
!
Date class understands how to manipulate dates and answer questions about dates (e.g., "What day of the week was July 4, 1776?" (Date readFrom: (ReadStream on: 'July 4, 1776')) weekday => Thursday )
Programming the AppointmentBook
AppointmentBook methodsFor: 'appointment alarms' stamp: 'mjg 9/27/1998 14:31'!
onToday
(appointments select: [:each | each date = Date today]) do: [:each | each on].
!
AppointmentBook methodsFor: 'appointment alarms' stamp: 'mjg 9/27/1998 14:36'!
allOff
appointments do: [:appointment | appointment alarm stop].
!
All collections understand do: , select: , collect: , reject:
Note: Not quite orthogonal!
Progamming the Appointments
Appointment methodsFor: 'accessing' stamp: 'mjg 9/27/1998 14:32'!
alarm: someTime
alarm _ AlarmClock new.
alarm setAlarmTime: someTime.
!
Appointment methodsFor: 'accessing' stamp: 'mjg 9/27/1998 14:18'!
alarm
^alarm
!
Skipping lots of accessors
Programming the Appointments
Appointment methodsFor: 'appointment alarms' stamp: 'mjg 9/27/1998 14:34'!
on
"The appointment is today, so turn on alarm."
alarm alarmBlock: [3 timesRepeat: [Smalltalk beep.].
Transcript show: 'Appointment: ',description.
alarm stop.].
alarm setTime: (Time now printString).
alarm start.
!
Testing AppointmentBook
We can test the AppointmentBook now.
b _ AppointmentBook new initialize.
b makeAppointment: 'Testing!' for: '9/27/98' at: '2:34 pm'.
b onToday.
When you're declaring the end of the day, be sure to use b allOff
Summarizing: Implementing Models
Attributes -> Instance Variables, and Services -> Methods
(At least, most of the time)
Gen-Spec -> Superclass-Subclass
Part-Whole can map several ways:
- Both sides know about each other, only one side knows about the other.
- If many-to-one, will need a collection to manage the mapping