Smalltalk Best Practice Patterns


By: K. Beck
Published in: Prentice Hall, 1997
Category: Smalltalk Idioms, Design Process

Summary: Patterns used by experienced, successful Smalltalk developers.

Pattern: Composed Method

Pages: 21-22

To divide a program into methods, realize that methods should perform one identifiable task. Keep all operations in a method at the same level of abstraction. This will produce programs with many small methods (a few lines long).

Pattern: Constructor Method

Pages: 23-24

To represent instance creation, provide methods to create well-formed instances and pass all required parameters to them.

Pattern: Constructor Parameter Method

Pages: 25-26

You're using Constructor Method. To set instance variables from the parameters, use a single method that sets all variables. Preface its name with "set" plus the names of the variables.

Pattern: Shortcut Constructor Method

Pages: 26-27

When Constructor Method is too wordy, represent object creation as a message to one of the arguments of the constructor method. Add no more than three of these to a system.

Pattern: Conversion

Pages: 28

To convert information from one object format to another, convert from one object to another rather than overwhelm any object's protocol.

Pattern: Converter Method

Pages: 28-29

To represent simple conversion of one object to another with the same protocol but different format, provide a method in the original object that performs the conversion. Name the method by prepending "as" to the class of the object returned.

Pattern: Converter Constructor Method

Pages: 29-30

To represent the conversion of one object to another with a different protocol, use Constructor Method with the object to be converted as an argument.

Pattern: Query Method

Pages: 30-32

To represent testing a property of an object, provide a method that returns a Boolean. Name it by prefacing the property name with a form of "be," e.g., is, was, will.

Pattern: Comparing Method

Pages: 32-33

To order objects with respect to each other, implement "<=" to return true if the receiver should be ordered before the argument.

Pattern: Reversing Method

Pages: 33-34

To code a smooth flow of messages, code a method on the parameter. Derive its name from the original message. Take the original receiver as a parameter to the new method. Implement the method by sending the original message to the original receiver.

Pattern: Method Object

Pages: 34-37

To code a method where many lines of code share many arguments and temporary variables, create a class named after the method with an instance variable for the receiver of the original method, each argument, and each temporary variable. Use Constructor Method and take the original receiver and the method arguments. Give it one instance method, #compute, implemented by copying the body of the original method. Replace the method with one that creates an instance of the new class and sends it #compute.

Pattern: Execute Around Method

Pages: 37-39

To represent pairs of actions that have to be taken together, code a method that takes a Block as an argument. Name the method by appending "During: aBlock" to the name of the first method to be invoked. In the body of the Execute Around Method, invoke the first method, evaluate the block, then invoke the second method.

Pattern: Debug Printing Method

Pages: 39-40

To code the default printing method, override printOn: to provide information about an object's structure.

Pattern: Method Comment

Pages: 40-43

To comment methods, at the beginning of the method communicate important information not obvious in the code.

Pattern: Message

Pages: 43-44

To invoke computation, send a named message. Let the receiver decide what to do with it.

Pattern: Choosing Message

Pages: 45-47

To execute one of several alternatives, send a message to one of several objects. Each object executes one alternative.

Pattern: Decomposing Message

Pages: 47-48

To invoke parts of a computation, send several messages to "self."

Pattern: Intention Revealing Message

Pages: 48-49

To communicate your intent when the implementation is simple, send a message to "self." Name the message so it communicates what is to be done, not how it is to be done. Code a simple method for the message.

Pattern: Intention Revealing Selector

Pages: 49-51

Name methods after what they accomplish.

Pattern: Dispatched Interpretation

Pages: 51-55

To allow two objects to cooperate when one wishes to conceal its representation, have the client send a message to the encoded object. Pass a parameter to which the encoded object will send decoded messages.

Pattern: Double Dispatch

Pages: 55-57

To code a computation that has many cases--the cross product of two families of classes, send a message to the argument. Append the class name of the receiver to the selector and send the receiver as an argument.

Pattern: Mediating Protocol

Pages: 57-59

To code the interaction between two objects that need to remain independent, refine the protocol between the objects to use consistent terms.

Pattern: Super

Pages: 59-60

To invoke superclass behavior, send a message to "super" instead of "self."

Pattern: Extending Super

Pages: 60-62

To add to a superclass implementation of a method, override the method and send a message to "super" in the overriding method.

Pattern: Modifying Super

Pages: 62-64

To change part of the behavior of a superclass method without modifying it, override the method and invoke "super."

Pattern: Delegation

Pages: 65-65

To allow an object to share implementation without inheritance, send part of its work to another object.

Pattern: Simple Delegation

Pages: 65-66

To invoke a disinterested delegate, delegate messages unchanged.

Pattern: Self Delegation

Pages: 67-69

To implement delegation to an object that needs reference to the delegator, send the delegator in an additional parameter called "for:."

Pattern: Pluggable Behavior

Pages: 69-70

To parameterize the behavior of an object, add a variable to trigger different behavior.

Pattern: Pluggable Selector

Pages: 70-73

To code simple instance-specific behavior, add a variable that contains a selector to be performed. Append "Message" to the Role Suggesting Instance Variable Name. Use Composed Method to simply perform the selector.

Pattern: Pluggable Block

Pages: 73-75

To use Pluggable Behavior when the code is not worth a separate class, add an instance variable to store a Block. Append "Block" to the Role Suggesting Instance Variable Name. Use Composed Method to evaluate the Block to invoke the Pluggable Behavior

Pattern: Collecting Parameter

Pages: 75-77

To return a collection that is the collaborative result of several methods, add a parameter to all the methods that collects the results.

Pattern: Common State

Pages: 80-81

To represent state that will have different values for all instances of a class, declare an instance variable in the class.

Pattern: Variable State

Pages: 82-83

To represent state that might not be present in all instances of a class, put variables that only some instances will have in a Dictionary stored in an instance variable called "properties." Implement "propertyAt:aSymbol" and "propertyAt:aSymbol put:anObject" to access properties.

Pattern: Explicit Initialization

Pages: 83-85

To initialize instance variables to their default values, implement a method "initialize" that sets all values explicitly. Override the class message "new" to invoke it on new instances.

Pattern: Lazy Initialization

Pages: 85-86

To initialize instance variables to their default values, use Getting Method for each variable. Initialize it if necessary using Default Value Method.

Pattern: Default Value Method

Pages: 86-87

To represent the default value of a variable, create a method that returns the value. Prepend "default" to the name of the variable to form the name of the method.

Pattern: Constant Method

Pages: 87-89

To code a constant, create a method that returns the constant value.

Pattern: Direct Variable Access

Pages: 89-91

To get and set an instance variable, access and set the variable directly.

Pattern: Indirect Variable Access

Pages: 91-93

To get and set an instance variable, use Getting Method and Setting Method

Pattern: Getting Method

Pages: 93-95

To provide access to an instance variable, provide a method that returns the value of the variable. Give it the same name as the variable.

Pattern: Setting Method

Pages: 95-96

To change the value of an instance variable, provide a method with the same name as the variable and a single parameter--the value to be set.

Pattern: Collection Accessor Method

Pages: 96-99

To provide access to an instance variable that holds a collection, provide methods implemented using Delegation to the collection. To name the methods, add the name of the collection to the collection messages.

Pattern: Enumeration Method

Pages: 99-100

To provide safe, general access to collection elements, implement a method that executes a Block for each element of the collection. Name the method by concatenating the name of the collection and "Do:."

Pattern: Boolean Property Setting Method

Pages: 100-101

To set a Boolean property, create two methods with names beginning with "be." One has the property name and the other the negation. Add "toggle" if the client doesn't want to know the current state.

Pattern: Role Suggesting Instance Variable Name

Pages: 102-103

Name an instance variable for the role it plays. Make it plural if the variable will hold a collection.

Pattern: Temporary Variable

Pages: 103-104

To save the value of an expression for use in a method, create a variable whose scope and extent is the method. Declare it just below the method selector. Assign it when the expression is valid.

Pattern: Collecting Temporary Variable

Pages: 105-106

Use a temporary variable to collect values to be used later in a method.

Pattern: Caching Temporary Variable

Pages: 106-108

To improve the performance of a method, set a temporary variable to the value of the expression when it's valid, then use the variable instead of the expression.

Pattern: Explaining Temporary Variable

Pages: 108-109

To simplify a complex expression in a method, remove a subexpression, assign its value to a temporary variable before the complex expression, then use the variable in the complex expression.

Pattern: Reusing Temporary Variable

Pages: 109-110

To reuse an expression in a method when its value may change, execute the expression once and set a temporary variable. Use the variable instead of the expression in the method.

Pattern: Role Suggesting Temporary Variable Name

Pages: 110-111

Name a temporary variable after the role it plays.

Pattern: Collection

Pages: 115-116

To represent a one-to-many relationship, use a Collection.

Pattern: Ordered Collection

Pages: 116-117

To code collections whose size can't be determined in advance, use an ordered collection as the default dynamically sized collection.

Pattern: Run Array

Pages: 118-119

To compactly code an ordered collection or array, use a run array to compress long runs of the same element.

Pattern: Set

Pages: 119-124

To code a collection whose elements are unique, use a set.

Pattern: Equality Method

Pages: 124-126

To code equality for new objects, define a method called "=." Protect the implementation of the method so only objects of compatible classes will be tested.

Pattern: Hashing Method

Pages: 126-128

To ensure that new objects work correctly with hashed collections, if you override "=," override "hash."

Pattern: Dictionary

Pages: 128-131

To map one kind of object to another, use a dictionary.

Pattern: Sorted Collection

Pages: 131-132

To sort a collection, use a sorted collection. Set its sort block to use a criterion other than "<=."

Pattern: Array

Pages: 133-135

To code a collection with a fixed number of elements, use an array. Create it with "new:anInteger" so it has space for the number of elements it needs.

Pattern: Byte Array

Pages: 135-137

To code an array of numbers in the range 0..255 or -128..127, use a byte array.

Pattern: Interval

Pages: 137-138

To code a collection of numbers in sequence, use an interval with start, stop, and an optional step value. Use methods Number>>to: and to:by: (see Shortcut Constructor Method) to build Intervals.

Pattern: Is Empty

Pages: 139-141

To see if a collection is empty, send isEmpty. Use notEmpty to see if a collection has elements.

Pattern: Includes:

Pages: 141-143

To search for an element in a collection, send includes: with the element as an argument.

Pattern: Concatenation

Pages: 143-144

To concatenate two collections, send "," to the first with the second as an argument.

Pattern: Enumeration

Pages: 144-145

To execute code across a collection, use enumeration messages.

Pattern: Do

Pages: 146-147

To execute code for each element in a collection, send do: to a collection to iterate over its elements. Pass a one argument block as the parameter. It will be evaluated once for each element.

Pattern: Collect

Pages: 147-149

To operate on the result of a message sent to each element of a collection, use collect: to create a new collection whose elements are the results of evaluating the block passed to collect:.

Pattern: Select/Reject

Pages: 149-150

To filter out part of a collection, use select: and reject: to return new collections. Enumerate the new collection. Both take a one argument block that returns a Boolean. Select: returns elements where block returns true. Reject: returns elements where block returns false.

Pattern: Detect

Pages: 150-152

To search a collection, send detect:. The first element for which the block argument evaluates to true will be returned.

Pattern: Inject:into:

Pages: 152-153

To keep a running value over a collection, use inject:into:. Make the first argument the initial value and the second argument a two-element block. Call the block arguments "sum" and "each." Have the block evaluate to the next value of the running value.

Pattern: Duplicate Removing Set

Pages: 154

To remove duplicates from a collection, send "asSet" to the collection. The result will have all duplicates removed.

Pattern: Temporarily Sorted Collection

Pages: 155

To present a collection with one of many sort orders, send "asSortedCollection" to get a sorted copy of the collection. Send "asSortedCollection:aBlock" for custom sort requests.

Pattern: Stack

Pages: 156-157

To implement a stack, use Ordered Collection

Pattern: Queue

Pages: 157-159

To implement a queue, use Ordered Collection

Pattern: Searching Literal

Pages: 159-161

To search for one of a few literal objects, ask a literal collection if it includes the element.

Pattern: Lookup Cache

Pages: 161-163

To optimize complex Detect or Select/Reject loops, prepend "lookup" to the name of the search method. Add an instance variable holding a Dictionary to cache results. Name the variable by appending "Cache" to the name of the search. Make the parameters of the search the keys of the dictionary and the results of the search the values.

Pattern: Parsing Stream

Pages: 164-165

To write a simple parser, put the stream in an instance variable. Have all parsing methods work from the same stream.

Pattern: Concatenating Stream

Pages: 165-166

You want to improve the performance of a Smalltalk system. To concatenate several collections efficiently, use a stream. See Concatenating Stream [Auer+96]

Pattern: Simple Superclass Name

Pages: 168-169

What do you call a class that is the root of an inheritance hierarchy? Use a single word that conveys its purpose in the design.

Pattern: Qualified Subclass Name

Pages: 169-170

Name a new subclass, by prepending an adjective to the superclass name.

Pattern: Inline Message Pattern

Pages: 172-174

To format the message pattern, avoid explicit line breaks.

Pattern: Type Suggesting Parameter Name

Pages: 174-175

Name a method parameter by using the most general expected class preceded by "a" or "an." If more than one parameter has the same expected class, precede the class name with a descriptive word.

Pattern: Indented Control Flow

Pages: 175-177

To indent messages, put zero or one argument messages on the same line as their receiver. For messages with two or more keywords, put each keyword/argument pair on its own line, indented one tab.

Pattern: Rectangular Block

Pages: 177-178

To format blocks, make blocks rectangular. Use the square brackets as the upper left and bottom right corners of the rectangle. If the statement is simple, the block can fit on one line. If the statement is compound, bring the block onto its own line and indent.

Pattern: Guard Clause

Pages: 178-179

To format code that shouldn't execute if a condition holds, format the one-branch conditional with an explicit return.

Pattern: Conditional Expression

Pages: 180-182

To format conditional expressions where both branches assign or return a value, format the expression so the value is used where it clearly expresses the intent of the method.

Pattern: Simple Enumeration Parameter

Pages: 182-183

Name the parameter to an enumeration block "each." If there are nested enumeration blocks, append a descriptive word to all parameter names.

Pattern: Cascade

Pages: 183-185

To format multiple messages to the same receiver, use a cascade to send several messages to the same receiver. Separate messages with a semicolon. Put each message on its own line and indent one tab. Use cascades for messages with zero or one argument.

Pattern: Yourself

Pages: 186-188

To use the value of a cascade if the last message doesn't return the receiver of the message, append the message "yourself" to the cascade.

Pattern: Interesting Return Value

Pages: 188-189

Explicitly return a value at the end of a method, when you want the sender to use the value.